please can someone help me in saving and loading its Dynamic array from a Stream
const
iGlobHolderCount = 100;
type
TFiLeSpec = record
iSize: Integer;
end;
TFileSpecLst = array of TFiLeSpec;
TFiLeSpecList = record
iMin: Integer;
iMax: Integer;
iCount: Integer;
FileSpecLst: TFileSpecLst;
end;
var
FFileSpec: array of TFiLeSpec;
FFileSpecList: array [1 .. iGlobHolderCount] of TFiLeSpecList;
Write first the length of an array, and next the array data:
type
TItem = Integer;
TItemArray = array of TItem;
var
Stream: TStream;
Arr: TItemArray;
L: LongWord;
begin
Arr:= TItemArray.Create(1, 2, 3);
// To save
Stream:= TFileStream.Create('C:\Temp\test.bin', fmCreate);
L:= Length(Arr);
Stream.WriteBuffer(L, SizeOf(L));
Stream.WriteBuffer(Pointer(Arr)^, L * SizeOf(TItem));
Stream.Free;
// To load
Stream:= TFileStream.Create('C:\Temp\test.bin', fmOpenRead);
Stream.ReadBuffer(L, SizeOf(L));
SetLength(Arr, L);
Stream.ReadBuffer(Pointer(Arr)^, L * SizeOf(TItem));
Stream.Free;
end;
Another solution, working from Delphi 5 up to XE2, is to use some features of one our core OpenSource unit.
In fact, it implements:
some low-level RTTI functions for handling record types: RecordEquals, RecordSave, RecordSaveLength, RecordLoad;
a dedicated TDynArray object, which is a wrapper around any dynamic array, able to expose TList-like methods around any dynamic array, even containing records, strings, or other dynamic arrays. It's able to serialize any dynamic array.
Serialization uses an optimized binary format, and is able to save and load any record or dynamic array as RawByteString.
You can code, e.g.
var
FFileSpec: array of TFiLeSpec;
TFileSpecList = array of TFiLeSpecList;
FFileSpecList: TFileSpecList;
var FSL: TDynArray;
Bin: RawByteString;
begin
FSL.Init(TypeInfo(TFiLeSpecList),FFileSpecList);
// ... then you use FFileSpecList[] as usual
// ... or use some methods of FSL:
if FSL.Count>0 then
FSL.Delete(0);
FSL.Add(FFileSpec);
FSL.Clear;
// then you can serialize the content to binary
Bin := FSL.SaveTo;
// use FSL.LoadFrom(Bin) to read the whole array content back
// or you can use a TStream
FSL.SaveToStream(aStream);
FSL.Clear;
aStream.Position := 0;
FSL.LoadFrom(aStream);
// you do not need to release nor Free FSL: this is a wrapper around FFileSpecList
end;
Note that I've replace your TFileSpecList by a dynamic array, but you may use a fixed array instead, inside a record to provide additional RTTI - then use RecordLoad / RecordSave functions. It will save the internal dynamic array content using RTTI (even with Delphi 5), handling any string or nested array within.
It's used by our mORMot framework (e.g. for serialization of dynamic arrays into the DB), but it's not part of it: just one unit, nor SQLite3 nor the whole ORM classes are needed.
See this page for additional information.
Related
I'm using Delphi 10.0 Seattle.
Suppose I have a record like this:
TmyRecord = record
a,b : string;
ar : array of string
end;
And a variable like this:
v : array of TmyRecord;
and some code like this:
SetLength(v,2);
SetLength(v[0].ar,3);
SetLength(v[1].ar,2);
SetLength(v[0].ar[0],10);
SetLength(v[0].ar[1],5);
SetLength(v[0].ar[2],7);
...
v[0].ar[0][0] := 'aaaa';
v[0].ar[0][1] := 'bbbb';
....
v[1].ar[1][0] := 'xxxx';
Will this statement:
SetLength(v,0);
free all of the occupied memory, or do I have to free it manually?
Dynamic array memory is automatically managed by Delphi and is released when they go out of scope.
If you need to release array memory sooner you can explicitly clear the array v and that will automatically release all memory including the ones occupied by ar member of your record.
You don't have to do anything else.
There are several ways to clear the dynamic array in Delphi:
Setting array length to 0 is one way to clear the array.
SetLength(v,0);
You can also clear v array by setting it to nil
v := nil;
or by using the intrinsic Finalize:
Finalize(v);
All of these have identical meaning.
I have these 10 numbers (one in each line) in a Text File and I need them to be sorted in a chronological order starting with the highest number. I wrote a code which works just fine, but the problem is that the code isn't flexible, because once I add another number to the Text File it won't work since the code is set to sort 10 numbers only...this is due to my array of integers which is supposed to read the values before the sorting process starts, but won't allow me to add a variable to the array's properties so it will be able to read and sort any-size text file...I know there has to be a way of making a program which can sort any-size file of this structure, just please tell me how I could improve my code. (If you think my way isn't too efficient, it's because that's my homework from high school and I need to use these arrays to implement a bubblesort).
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils;
var
numbers, sortednumbers : TextFile;
count : integer=0;
number : array[1..10] of integer;
I : integer;
Procedure Swap(var X, Y : Integer);
var
Temp : integer;
begin
Temp := X;
X := Y;
Y := Temp;
end;
procedure Assign;
var I : Integer;
begin
reset(numbers);
for I := 1 to count do ReadLn(numbers, number[I]);
end;
procedure BubbleSort;
var I, J : integer;
begin
for I := 2 to count do
begin
for J := count downto I do
if (number[J] > number[J - 1]) then
Swap(number[J - 1], number[J]);
end;
end;
begin
AssignFile(numbers, 'Numbers.txt');
AssignFile(sortednumbers, 'Sorted-Numbers.txt');
Reset(numbers);
While not EoF(numbers) do
begin
ReadLn(numbers);
Inc(count);
end;
Reset(numbers);
ReWrite(sortednumbers);
Assign;
BubbleSort;
For I := 1 to count do writeln(sortednumbers, number[I]);
CloseFile(numbers);
CloseFile(sortednumbers);
end.
Use a dynamic array. This is an array that can change the number of elements it holds.
Instead of declaring:
number : array[1..10] of integer;
instead skip the bounds declaration:
Number : array of integer;
Then, before you start using it, set its length:
SetLength(Number, 10);
Once you are done, free the memory by setting it to have length 0:
SetLength(Number, 0);
Note that:
Indexes of a dynamic array start from 0, not 1. That is, Number[0] is the first element and Number[9] is the tenth. This is quite common in programming but can be confusing if it's not something you've come across before.
There are other 'quality' changes you can make to your code too. As commenter David Heffernan said, stop using global variables. I would also suggest using try/finally for resource allocation and cleanup, and using streams (and another link, wrap this over TFileStream) instead of the old-style file IO you're currently doing. Also, consider your variable names - Number for an array of numbers is odd - why not add the S and call it Numbers, for example?
That might be a bit much all at once, so go slowly in small steps, save and backup often (preferably into source control), and have fun!
And if it had not been a school project:
function Comparer(List: TStringList; index1, index2: integer): integer;
begin
try
Result:= StrToInt(List[index1]) - StrToInt(List[Index2]);
except
raise Exception.Create('Your file does not contain numbers');
end;
end;
function SortNumbers
var
lStringList: TStringlist;
begin
lStringList := TStringlist.create;
try
lStringList.LoadFromFile('Numbers.txt');
lStringList.CustomSort(Comparer);
lStringList.SaveToFile('Sorted-Numbers.txt');
finally
lStringList.Free;
end;
end;
However the Delphi RTL uses Quicksort, not Bubblesort.
I asked another question about using records with operators. During testing, I've discovered an anomaly where two instances of this type seem to share the same memory.
The record has an array of Integer...
type
TVersion = record
Values: array of Integer;
function Count: Integer;
class operator implicit(aVersion: TVersion): String;
class operator implicit(aVersion: String): TVersion;
end;
class operator TVersion.implicit(aVersion: TVersion): String;
var
X: Integer;
begin
Result:= '';
for X := 0 to Length(aVersion.Values)-1 do begin
if X > 0 then Result:= Result + '.';
Result:= Result + IntToStr(aVersion.Values[X]);
end;
end;
class operator TVersion.implicit(aVersion: String): TVersion;
var
S, T: String;
I: Integer;
begin
S:= aVersion + '.';
SetLength(Result.Values, 0);
while Length(S) > 0 do begin
I:= Pos('.', S);
T:= Copy(S, 1, I-1);
Delete(S, 1, I);
SetLength(Result.Values, Length(Result.Values)+1);
Result.Values[Length(Result.Values)-1]:= StrToIntDef(T, 0);
end;
end;
function TVersion.Count: Integer;
begin
Result:= Length(Values);
end;
Now I try to implement this...
var
V1, V2: TVersion;
begin
V1:= '1.2.3.4';
V2:= V1;
ShowMessage(V1);
ShowMessage(V2);
V2.Values[2]:= 8;
ShowMessage(V1);
ShowMessage(V2);
end;
I expect that the V2 should be 1.2.8.4 and V1 to remain 1.2.3.4. However, V1 also changes to 1.2.8.4.
How do I keep these records independent when I assign them?
This behaviour is by design. A dynamic array variable is a pointer. When you assign a dynamic array variable you take a copy of the pointer and increase the reference count. Of course this also happens when you assign compound structures that contain dynamic arrays.
The documentation covers this:
If X and Y are variables of the same dynamic-array type, X := Y points X to the same array as Y. (There is no need to allocate memory for X before performing this operation.) Unlike strings and static arrays, copy-on-write is not employed for dynamic arrays, so they are not automatically copied before they are written to.
To get a mental model for this, this of dynamic arrays as being references in the same way as classes and interfaces. This is in contrast to simple types (integer, double etc.), strings, records, etc.
The standard way to deal with this is as follows:
Make the dynamic array private.
Expose the contents of the array through properties.
In the element setter, make sure that the reference to the dynamic array is unique.
That last step is the tricky part. Make a dynamic array reference unique by calling SetLength:
SetLength(arr, Length(arr));
One of the promises that SetLength makes is that it will always make its first argument be unique.
This has the effect of implementing copy-on-write. To the best of my knowledge it is not possible to implement copy-on-assign because the compiler gives you no hook into the assignment operator.
So the answer to:
How do I keep these records independent when I assign them?
is that you cannot. You need to use copy-on-write instead.
Is there any simple univesal way to add 2 arrays into one? In the case below it is not possible simply use C := A + B statement...
I would like to avoid making algorhytm for it everytime .
TPerson = record
Birthday: Tdate;
Name, Surname:string;
end;
Tpeople = array of TPerson;
var A, B, C:Tpeople;
C:=A+B; // it is not possible
thanx
Due to the two string fields in each TPerson record, you can't just use binary "move", since you'll mess the reference counting of string - especially in a multi-threaded environment.
You can do it manually - this is fast and nice:
TPerson = record
Birthday: TDate;
Name, Surname: string;
end;
TPeople = array of TPerson;
var A, B, C: TPeople;
// do C:=A+B
procedure Sum(const A,B: TPeople; var C: TPeople);
begin
var i, nA,nB: integer;
begin
nA := length(A);
nB := length(B);
SetLength(C,nA+nB);
for i := 0 to nA-1 do
C[i] := A[i];
for i := 0 to nB-1 do
C[i+nA] := B[i];
end;
Or you can use our TDynArray wrapper, which has a method for handling such cases:
procedure AddToArray(var A: TPeople; const B: TPeople);
var DA: TDynArray;
begin
DA.Init(TypeInfo(TPeople),A);
DA.AddArray(B); // A := A+B
end;
The AddArray method can add a sub-port of the original array:
/// add elements from a given dynamic array
// - the supplied source DynArray MUST be of the same exact type as the
// current used for this TDynArray
// - you can specify the start index and the number of items to take from
// the source dynamic array (leave as -1 to add till the end)
procedure AddArray(const DynArray; aStartIndex: integer=0; aCount: integer=-1);
Note that with such records, it will use the System._CopyRecord RTL function, which is not so optimized for speed. I've written a faster version - see this blog article or this forum thread.
If you use dynamic arrays in functions/procedures, don't forget to use explicitly const or var parameters (as I coded above), otherwise it will make a temporary copy at each call, therefore it may be slow.
There is nothing built in that allows dynamic arrays to be concatenated.
You may consider using one of the generic container classes found in Generics.Collections, TList.
In your case you would have 3 instances of TList, say A, B and C. Then you could write
A.Clear;
A.AddRange(B);
A.AddRange(C);
I think this is as close as you can get to what you want with what is delivered out of the box.
If you are prepared to do a bit of coding yourself then you could make use of operator overloading to use the exact syntax you requires. Declare a record containing an array of TPerson with private visibility. You then need to implement an Add operator, a Count property and a default Items[] property. This could be made generic too so you only need write it once.
TTurboArray = record<T>
private
FItems: array of T;
//property accessors here
public
class operator Add(a, b: TTurboArray<T>): TTurboArray<T>;
property Count: Integer read GetCount write SetCount;
property Items[Index: Integer]: T read GetItem write SetItem; default;
end;
This idea can be extended into a very powerful data structure as you see fit.
There is a quick-and-dirty way to do this. It is a terrible hack, but it should work and even take care of reference counting:
function ConcatPeople(const A, B: TPeople): TPeople;
var
Temp: TPeople;
ALen, BLen: Integer;
begin
Result := Copy(A);
BLen := Length(B);
if BLen = 0 then
Exit;
ALen := Length(A);
Temp := Copy(B);
SetLength(Result, ALen + BLen);
Move(Temp[0], Result[ALen], BLen * SizeOf(B[0]));
FillChar(Temp[0], BLen * SizeOf(B[0]), 0);
end;
In effect, the data in Temp are "swapped" with the empty records in Result, so the balance is maintained and refcounting will keep on working.
Update
For what it is worth: This is aparently the same technique as used in this accepted SO answer and in, e.g. TList<T>.Insert. I had deleted this answer, but I still think it is valid, so I undeleted it again. It could do with a lock around the Move/FillChar block, so no one accesses the items when they are being "swapped". I'll add that.
Here's how I handled it, though it required a slight (but hopefully immaterial to you) modification to your original code to use TArray:
(tested in XE2)
uses
Generics.Collections;
type
TArrayExt = class(TArray)
class function Concat<T>(const First, Second: array of T): TArray<T>; overload;
end;
class function TArrayExt.Concat<T>(const First, Second: array of T): TArray<T>;
var
i: Integer;
l: Integer;
begin
l := Length(First);
SetLength(Result, l + Length(Second));
for i := Low(First) to High(First) do
Result[i] := First[i];
for i := Low(Second) to High(Second) do
Result[l + i] := Second[i];
end;
type
TPerson = record
Birthday: TDate;
Name, Surname: String;
end;
TPeople = TArray<TPerson>;
var
A, B, C: TPeople;
begin
C := TArrayExt.Concat<TPerson>(A, B);
The main difference here is that I use "TArray" rather than "array of TPerson". This can be used for arrays strings, records, etc. I find the main benefit of doing it this way is that it's truly making a copy rather than a move. And I am using the "normal" Delphi functions instead of things like bulk memory copies, which can give me the willies.
Of course, if you were doing this in a tight loop and needed the performance, this way might not be best for you. But I think this is the best for most other situations, especially in terms of maintenance and readability.
(Unless someone posts a comment about how there's some horrible hidden memory leak here. Hopefully not!)
You code works fine in the newest version of delphi C := A+B;.
But for dynamic arrays in older versions you can use the function concat. Example:
C := Concat(A, B);
I have two Delphi7 programs: a COM automation server (EXE) and the other program which is using the automation server.
I need to pass an array of bytes from one program to the other.
After some searching I've found that using variant arrays is the way to go (correct me please if you know any better methods).
My question is:
How do I create a variant array in one program, and then how do I read its values in the other?
I know about VarArrayCreate and VarArrayLowBound/VarArrayHighBound, but I'm unsure on how to do this properly.
Thanks!
You create it like that:
Declarations first
var
VarArray: Variant;
Value: Variant;
Then the creation:
VarArray := VarArrayCreate([0, Length - 1], varVariant);
or you could also have
VarArray := VarArrayCreate([0, Length - 1], varInteger);
Depends on the type of the data. Then you iterate like this:
i := VarArrayLowBound(VarArray, 1);
HighBound := VarArrayHighBound(VarArray, 1);
while i <= HighBound do
begin
Value := VarArray[i];
... do something ...
Inc(i);
end;
Finally you clear the array when you don't need it anymore. EDIT: (This is optional, see In Delphi 2009 do I need to free variant arrays? )
VarClear(VarArray);
That is all there is to it. For another example look at the official Embracadero Help
EDIT:
The array should be created only once. Then just use it like shown in the above example.
For the other side:
(assuming Value is the Variant parameter and the element type is WideString)
var
Source: PWideStringArray;
if VarIsArray(Value) then begin
Source:= VarArrayLock(Value);
try
for i:= 0 to TVarData(Value).VArray^.Bounds[0].ElementCount - 1 do
DoWhatEverYouWantWith(Source^[i]);
end;
finally
VarArrayUnlock(Value);
end;
end;