I have inherited this code:
var
FSavedRecords : Variant; { actually, a private property in an ancestor }
lFieldsArray : Variant;
lClientDataSet: TClientDataSet;
FSavedRecords := VarArrayCreate([0, lCount], varVariant);
for lRow := 0 to lCount do
begin
FSavedRecords[lRow] := VarArrayCreate([0, lClientDataSet.FieldCount-1], varVariant);
with lClientDataSet do
begin
lFieldsArray := FSavedRecords[lRow];
if <SomeCondition> then
put lClientDataSet field values into lFieldsArray
Since the condition is not always true, I end up with fewer than lCount(+1) elements in FSavedRecords.
I can count those of course (say: lNrOutput), but cannot do a SetLength(FSavedRecords,lNrOutput) ('Constant object cannot be passed as a var parameter').
If SetLength() cannot be used, I assume I can convert the variant array to a dynamic 'array of Variant' with DynArrayFromVariant and use SetLength on that, but this has the disadvantage of the extra copy operation. I would like to re-use the private FSavedRecords from an ancestor form, which is used in other places in the program for the same purpose.
Is there maybe a better way out?
SetLength is used to resize dynamic arrays and long strings. To resize a variant array you use VarArrayRedim.
Another option is to build your list of elements in a temporary container of type TList<T>. When you are finished, you can use the Count property of that container to size the variant array once and for all. And then you'd copy across the actual values.
I think it makes little difference which approach you use.
Related
I want to sort a TArray containing entries of a self defined TPair<>. I tried to follow this one, but the compiler always complains that he needs an object record or clastype (E2018):
How to sort a generic array containing records.
My code:
type
TFailureEntry = TPair<System.Word, TMyFailureRecord>;
procedure TMyClass.GetFailureAbbreviations;
var
FailureArray: TArray<TFailureEntry>;
Comparison: TComparison<TFailureEntry>;
begin
// derrive the array contents from a dictionary
FailureArray := FFailureDictionary.ToArray;
Comparison :=
function(const Left, Right: TFailureEntry): Integer
begin
Result := Left.Key-Right.Key;
end;
FailureArray.Sort(TComparer<TFailureEntry>.Construct(Comparison));
end;
The compiler complains at the .sort call.
An array cannot have a method - in this case no Sort method. Use TArray.Sort instead:
TArray.Sort<TFailureEntry>(FailureArray, TComparer<TFailureEntry>.Construct(Comparison));
I created two functions to import (make) an array from a text file. They have the same function names but different number of parameters. They also have return values which are different since one importArray function is returning a 1D array and the other is returning a 2D array.
Overloads Function importArray(fileName As String) As Array
Overloads Function importArray(fileName As String, splitter As Char) As Array
Sub Main()
Dim getArray As New MakeArray
Dim printArray() As String = getArray.importArray("array.txt")
For i = 0 To printArray.Length - 1
'printArray
Next
Console.ReadKey()
End Sub
I can't seem to wrap my head around this. I can enter 2 parameters when calling the function or 1 then it's fine, but I don't know how I could specify which function to call, because when I am printing the array I don't know whether to use the 1D or 2D array. I can't do 2 for loops since using one dimension or 2 throws an error "Expression is not a method" so I'm not sure how I can get around this.
Is there a way I could determine whether I am using a 1D or 2D array by reading the text file? I wanted to keep the code as efficient as possible.
Thank you!
Firstly, don't use the Array type that way. If the method return String array, that should be the return type. If it returns a 2D String array then that should be the return type.
Overloads Function ImportArray(fileName As String) As String()
Overloads Function ImportArray(fileName As String, splitter As Char) As String(,)
When you call one of the functions, you assign it to a variable of the appropriate type for the method you're calling. Just think of them as two different methods. Then, you either use a single loop of two nested loops to traverse the data.
Dim arr1 As String() = getArray.ImportArray(fileName)
For i = 0 To arr1.GetUpperBound(0)
'...
Next
Dim arr2 As String(,) = getArray.ImportArray(fileName, splitter)
For i = 0 To arr2.GetUpperBound(0)
For j = 0 To arr2.GetUpperBound(1)
'...
Next
Next
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.
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.
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;