Is there an easy way to clone a string array? - delphi

I have an array declared as:
const A: array[0..3] of ShortString = (
'Customer',
'Supplier',
'Stock',
'GL'
);
var B: array of ShortString;
I would like to clone the string array A to another array B. Using Move or Copy function doesn't work. Is there a fast and easy way to clone the array without using for loop?

The problem you face is that your constant A and your variable B are actually of different types. This can be demonstrated most easily by showing how you would declare a const and a var of the same type in a fashion equivalent to what you show in your question:
type
TSA = array[0..3] of ShortString;
const
A: TSA = (
'Customer',
'Supplier',
'Stock',
'GL');
var B: TSA;
With these declarations you could then simply write:
B := A;
But when A is a dimensioned array and B is a dynamic array, this isn't possible and your only option is to SetLength(B) as required and copy the elements one-by-one.
Although the const and the var types may look like they are the same - or compatible types - they are not, and this then is no different from trying to assign an Integer constant to a String variable... even though you know the simple conversion required to achieve it, the compiler cannot presume to guess that you intended this, so you have to be explicit and provide the conversion code yourself.

Something like:
SetLength(B, Length(A));
for i := Low(A) to High(A) do
B[i] := A[i];
Or in a more generic way:
type
TStringArray = array of ShortString;
procedure CloneArray(const source: array of ShortString; var dest: TStringArray);
var
i: integer;
begin
SetLength(dest, Length(source));
for i := Low(source) to High(source) do
dest[i] := source[i];
end;
In the latter case you'll have to redeclare B as B: TStringArray.

Related

Converting a generic TArray into a varArray

I'm trying to convert an array of T into a Variant (varArray).
With not-generic types (i.e: Integer), I'm using the following function:
function ToVarArray(AValues : array of Integer) : Variant;
var
i : integer;
begin
Result := VarArrayCreate(
[Low(AValues), High(AValues)],
varInteger
);
for i := Low(AValues) to High(AValues) do
Result[i] := AValues[i];
end;
I'm having some problems while trying to do the same thing with a generic TArray:
uses
System.Generics.Collections;
type
TArray = class(System.Generics.Collections.TArray)
public
class function ToVarArray<T>(const AValues: array of T) : Variant; static;
end;
I've tried the following:
class function TArray.ToVarArray<T>(const AValues: array of T) : Variant;
var
i : integer;
Tmp : T;
begin
Result := Tmp;
Result := VarArrayCreate(
[Low(AValues), High(AValues)],
VarType(Result)
);
for i := Low(AValues) to High(AValues) do
Result[i] := AValues[i];
end;
But It produces the following compile error at each row where I'm assigning a T to a Variant:
[dcc32 Error] Unit1.pas(36): E2010 Incompatible types: 'Variant' and
'T'
The title of the question says that you'd like to process generic TArray<T>, but the first sentence say it's array of T. You might think that both terms refer to the same data structure (dynamic array), and most of the time they do, but they make difference if you use them in place of a procedure/function argument declaration.
Therefore the following methods have different signatures and accept different types of parameters:
class function TArray.ToVarArray<T>(const AValues: TArray<T>): Variant;
class function TArray.ToVarArray<T>(const AValues: array of T): Variant;
While the first invariant accepts true dynamic array as a parameter, the latter takes an open array. This unfortunate language design is regular source of confusion for Delphi developers.
Delphi RTL already contains function that converts dynamic array to variant array of appropriate type - DynArrayToVariant. It takes pointer to initial element of dynamic array and array's type information as its arguments. To make use of generics you would write:
uses
System.Variants;
class function TArray.DynArrayToVarArray<T>(const AValues: TArray<T>): Variant;
begin
DynArrayToVariant(Result, #AValues[0], TypeInfo(TArray<T>));
end;
Your code would fail to compile if you try to use this routine with a static array:
var
SA: array[0..2] of Integer;
begin
SA[0] := 0;
SA[1] := 1;
SA[2] := 2;
{ E2010 Incompatible types: 'System.TArray<System.Integer>' and 'array[0..2] of Integer' }
TArray.DynArrayToVarArray<Integer>(SA);
end.
Open array solves this issue, but you need to "convert" it to dynamic array in order to use it with RTL's DynArrayToVariant.
class function TArray.OpenArrayToVarArray<T>(const AValues: array of T): Variant;
var
LArray: TArray<T>;
Index: Integer;
begin
SetLength(LArray, Length(AValues));
for Index := Low(AValues) to High(AValues) do
LArray[Index] := AValues[index];
Result := DynArrayToVarArray<T>(LArray);
end;
var
DA: TArray<Integer>;
SA: array[0..2] of Integer;
begin
DA := [0, 1, 2];
SA[0] := 0;
SA[1] := 1;
SA[2] := 2;
{ these all work }
TArray.OpenArrayToVarArray<Integer>(DA); // dynamic array
TArray.OpenArrayToVarArray<Integer>(SA); // static array
TArray.OpenArrayToVarArray<Integer>([0, 1, 2]); // open array constructor
end.
The above OpenArrayToVarArray<T> routine is pretty ineffective both in terms of performance and memory usage, because it copies elements one by one from open array to dynamic array. If you really need to support open arrays you should probably write better implementation inspired by RTL's DynArrayToVariant, however I find that one to be sub-optimal too.

Typecasting dynamic array over static one in Delphi

Q: Is it safe to cast dynamic array over a static one ?
type
Bytearray = array of byte;
function calcCRC(buf1: pointer) : dword;
var
buf: ByteArray ;
outbuffer : array [1..30] of byte;
begin
buf := bytearray(buf1^); // <- is it safe ?
outbuffer[1] := buf[0];
end;
procedure test;
var
testarr : array [1..30] of byte ;
begin
calccrc(#testarr);
end ;
Such snippet compiles and works correctly, even in quite large program with FastMM4. But something tells me, that it can be dangerous ...
Any advice ?
I guess you need to revert your logic. Casting a static array into a dynamic array is not safe at all (it has a counter and reference count before the actual data), whereas the opposite - casting a dynamic array into a static array pointer - is perfectly safe, if you take care of the buffer length.
Your initial code will trigger some random GPF (Access Violations) due to the fact that the reference counter will be modified in calcCRC() in some hidden code - look at the ASM using F2: there is more than you may expect, especially a call DynArrayClear which is pretty unsafe. You didn't have any problem yet because of what was in your memory. But if e.g. a 1 integer is stored before the data, it would trigger a GPF, since leaving calcCRC() would let the Delphi RTL try to release the dynamic array instance.
If you expect to access bytes of memory using indexes, you need to use a pointer to a static array, not a dynamic array.
The code could be the following:
Type
TByteDynArray = array of byte ;
TByteArray = array[0 .. (maxInt div sizeof(byte)) - 1] of byte;
PByteArray = ^TByteArray;
function calcCRCptr(buf1: PByteArray; buf1len: integer): dword;
var
outbuffer : array [1..30] of byte;
begin
result := 0;
// ensure you don't create any access violation by using big indexes
if buf1len < 1 then
exit; // avoid GPF
outbuffer[1] := buf1[0];
...
end;
function calcCRCdynarray(const buf1: TByteDynArray): dword;
begin
// you can use length(buf1) to get the number of items/bytes
result := calcCRCptr(pointer(buf1), length(buf1));
end;
procedure test ;
var
testdynarr: TByteDynArray;
teststaticarray: array[0..10] of byte;
begin
Setlength(testdynarr, 100);
calccrcdynarray(testdynarr) ; // safe
calccrcptr(pointer(testdynarr), length(testdynarr)); // direct call
calccrcptr(#teststaticarray, 11); // OK
end;
Also ensure that you don't mess with pointers, e.g. that you name your variables with proper Delphi convention about values (T...) and pointers (P....). Also follow the same convention to distinguish static arrays and dynamic arrays in your code.
This approach is certainly not safe. The static array has a different memory layout from the dynamic array. The dynamic array has metadata containing reference count and length. You might get away with it in this short excerpt but it is not a technique to be recommended.
Whatever the problem is, this is not the solution. Possible solutions might involve working with open arrays or pointers to byte.

Delphi Memory copy with Record to another Record

I am having problem with the logic. I have no idea how to copy record to another record in Delphi.
TypeA = record
value1 : word;
value2 : word;
value3 : word;
end;
TypeB = record
b1 : byte;
b2 : byte;
end;
I have my two records TypeA and TypeB. For example, I found out that data in TypeA record is belong to TypeB records. Note: TypeA has longer Data Length.
Question: How do i copy TypeA memory and place it in TypeB record?
CopyMemory(#TypeA, #TypeB, Length(TypeB))
When i tried CopyMemory and got an error (Incompaible types).
PS: I don't want to copy or assign it like below.
TypeB.b1 := TypeA.value1 && $FF;
TypeA and TypeB are just example records. Most of thecases, record TypeA and TypeB may contain multple records and it
will be harder to allocate form TypeA and assign to TypeB record.
Thanks in advance
----Addition question:
Is there a way to copy Delphi record to Byte array and how? If there is,
TypeA record to Byte Array
Byte Array to Type B
Will this logic work?
CopyMemory(#a, #b, SizeOf(TypeB))
if a is of type TypeA and b is of type TypeB.
Variant records:
TypeA = packed record
value1 : word;
value2 : word;
value3 : word;
end;
TypeB = packed record
b1 : byte;
b2 : byte;
end;
TypeAB = packed record
case boolean of
false:(a:TypeA);
true:(b:TypeB);
end;
..
..
var someRec:TypeAB;
anotherRec:TypeAB;
..
..
anotherRec.b:=someRec.b
procedure CopyAtoB( const A: TypeA; var B: TypeB);
begin
// Assume A is bigger than B.
Move( A, B, SizeOf( TypeB))
end;
or (with Math unit)
procedure CopyAtoB( const A: TypeA; var B: TypeB);
begin
// No assumptions about A, B sizes.
FillChar( B, SizeOf( B), 0);
Move( A, B, Min( SizeOf( TypeA), SizeOf( TypeB)))
end;
to copy A record to B record there is a good way - operator overloading; in this case you can simply overload Implicit and Explicit operations and write code likeb := a:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses System.SysUtils;
type
TTypeA = record
value1 : integer;
value2 : integer;
value3 : integer;
end;
TTypeB = record
b1 : byte;
b2 : byte;
class operator Implicit(value : TTypeA):TTypeB;
end;
class operator TTypeB.Implicit(value: TTypeA): TTypeB;
begin
result.b1 := Hi(value.value1);
result.b2 := Lo(value.value1);
end;
var a : TTypeA;
b : TTypeB;
begin
b := a;
end.
you can use TBytesStream to copy record to byte array:
var a : TTypeA;
bs : TBytesStream;
bArr : TArray<byte>;//array of byte;
begin
bs := TBytesStream.Create();
bs.Write(a, sizeof(a));
bArr := bs.Bytes;
end;
or simply set size of byte array to sizeof(A) and then copyMemory
All the other answers are variations of copying memory directly, but I think that is the wrong approach. It's low-level and you're using a high-level language, it's easy to make mistakes, and it is risky if your records contain managed data types.
If you're trying to get the first two bytes of one element of a record, try using a variant record (in C, a union.) It is a section of a record that can be addressed several ways, and here you want to address it either as a word or series of bytes.
Try something like this (untested, just typed in the SO editor):
type
TTwoBytes : packed record
ByteA, ByteB : Byte;
end;
TValueRecord : packed record:
case Boolean of
True: (Value: SmallInt);
False: (ValueBytes : TTwoBytes);
end;
end;
TMyRecord = packed record
Value1, Value2, Value3 : TValueRecord;
end;
Then for code:
var
MyRecord: TMyRecord;
MyBytes: TTwoBytes;
begin
MyRecord := ...; // Fill it with data here
// Access the words / smallints by something like: MyRecord.Value1.Value
MyBytes := MyRecord.ValueBytes; // The key bit: type safe assignment
// Do something with MyBytes.ByteA or MyBytes.ByteB
end;
What does this give you that's better than directly copying memory?
It's safe: if you have more complicated records containing strings, interfaces etc, it will still work without breaking reference counts.
It's type safe: no direct copying of memory: you and the compiler both know what you have and are using.
It's canonical: this is probably 'the Delphi way' to do it. (Although there are several ways to structure the records - if you look in the answer history I had a worse one originally. Also I think the 'case ... of' syntax for this is silly, but that's another discussion... :))
Some notes:
I changed your Word to SmallInt since I find Word confusing - it's not actually the 'word size' of the machine, it's 16 bits.
Also, I think this is a good article on variant records.
I used 'packed record', the justification for this is... iffy.
You can omit CopyMemory() (Windows unit) use by:
type
PTypeA = ^TTypeA;
TTypeA = record
value1 : word;
value2 : word;
value3 : word;
end;
PTypeB = ^TTypeB;
TTypeB = record
b1 : byte;
b2 : byte;
end;
var
A: TTypeA = (value1 : 11; value2 : 22; value3 : 33);
B: TTypeB;
B1: TTypeB;
C: {packed?} array of Byte;
begin
Assert(SizeOf(TTypeA) >= SizeOf(TTypeB));
//...
B:= PTypeB(#A)^;
//...
SetLength(C, SizeOf(A));
// TTypeA record to Byte Array
PTypeA(#C[0])^:= A;
// Byte Array to TTypeB
B1:= PTypeB(#C[0])^
end;
Use MOVE instead of CopyMemory if you want to simply copy the bytes from one place to another.
And to get the size of a record use SizeOf instead of Length.

Pass static byte array in parameter in Delphi

I have a static array:
myArray: array [0..15] of byte;
I wish to pass this array (not a copy of the array) into a function. Inside the function, I will manipulate the data in array.
Different lengths of STATIC byte array may be passed in to Init at different times. Therefore, I do not wish to declare Init to be able to receive only a certain length of byte array. (e.g. in this case it is a 16 bytes. At other times it may be 65000 bytes)
This is the way I used the function:
Init(#myArray[0]); //passing in pointer to static array
I tried to declare Init like this:
procedure Init(x: array of byte);
begin
//Do some stuff with x, for e.g.
Length(x); //I do not intend to pass in the length as a parameter,
//but determine it in this function itself.
end;
Is this also a correct way of declaring the Init function parameter?
Instead of Init(#myArray[0]) use Init(myArray). Delphi will transfer the myArray by reference
You declared x as open array of bytes. The open array parameters are type compatible with the array variables with the same element type. Delphi will transfer fixed array length as a hidden parameters. You can access it with Length(x) and e.g. for i := Low(x) to High(x) do.
Open array parameters cannot be set to nil. If you need to set x to nil, then you have to declare x as pointer to Byte or pointer to array[..] of Byte.
For static arrays there is no information about their length at runtime, but only at compile time.
So if you loose the type information at passing a static array to a function, there is no way to find out the length of the array within the function.
=> If you want to use static arrays with variable sizes, you must pass the size as additional parameter.
The clean way would be to work with pointer aritmetic rather than arrays here.
PByte = ^byte;
procedure Init(arr: PByte; len: Integer);
var
i: Integer;
begin
//Write 0, 1, 2 ... to arr
for i:= 0 to len-1 do
begin
arr^:= i;
Inc(arr);
end;
end;
Unclean solution:
TByteArray = array [0..$FFFFFFFF] of Byte;
PByteArray = ^TByteArray;
procedure Init(arr: PByteArray; len: Integer);
begin
arr^[0]:= 0;
arr^[1]:= 1;
...
end;
...
var
myAray: array[0..10] of byte;
begin
Init(PByteArray(#myArray[0]), 10);
end;
First, declaring
procedure Init(x: array of byte);
declare an open array parameter (not a dynamic array, unlike some other stated).
To do what you want, you need to declare like this :
type
TMyArray: array [0..15] of byte;
var
MyArray : TMyArray;
Procedure Init(var X : TMyArray)
Still, I'm not sure why you try to set the variable to nil. A static array variable is a little bit like a record, you can't set it to nil. And IF you pass a pointer to the function instead, you will need to assign your array to a variable first anyway(or allocate the memory directly in the function). Like this :
type
TMyArray : Array[0..15] of byte;
pTMyArray : ^TMyArray;
Procedure Init(var X : pTMyArray);
procedure DoSomething;
var P : pTMyArray;
begin
New(P); //or if you array is global, simply do "P := #MyArray"
try
//code here
Init(P);
//code here
finally
Dispose(P);
end;
end
But that would be a bad construct IMO, because if you set your array pointer to nil inside the Init function, you will get a memory leak. So this is pretty bad to start with. But I guess if it's your Init function that reserve the memory that would be OK.
If you settle for an open array parameter, I'd suggest using the Low and High to determine the bounds of the array.
I think that's the best info I can give you until you give more detail on what you are trying to achieve.
EDIT
Ok, if you need the function to accept an array of different size, you need an open array parameter.
You did it right in your exemple. But you can't set it to nil within the function.
I don't think it's possible to pass to a function a pointer to an open array type. If you really want a pointer to set it to nil, you will need to pass it as PByteArray... But you will need to also pass the size of the array.
first when your are declarating this
procedure Init(x: array of byte);
you are using a dynamic array open array parameter instead of a static array.
to pass a static array as a parameter , you must define a type
like this
type
Arr15 = array [0..15] of byte;
and then declare the procedure like this, using the keyword var to pass by reference.
procedure Init(var x: Arr15);
about the nil assign , you cannot set to nil a static array.
another alternative is use a dynamic array
check this sample
type
ByteArray = array of byte;
procedure Init(var x : ByteArray);
const
LenArray=15;
var
i : Byte;
begin
SetLength(x,LenArray); //alocate the memory and set the length of the array.
for i:=0 to LenArray-1 do //fill the array
x[i]:=i;
Writeln(Length(x)); //show the len of the array
SetLength(x,0); //now dealocates de memory
Writeln(Length(x)); //show the len of the array again
end;
Var
myArray : ByteArray;
begin
try
Init(myArray);
Readln;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.

Pass a multidimensional array as a parameter in Delphi

I'd like to pass a multi-dimensional array to a constructor like so:
constructor TMyClass.Create(MyParameter: array of array of Integer);
begin
LocalField := MyParameter;
end;
Where LocalField is an array of array of Integer.
However the above code won't compile ('Identifier expected but ARRAY found'). Could somebody explain to me why this is wrong? I tried reading up on open, static and dynamic arrays but have yet to find something that works. Is there a way to fix it without changing the type of LocalField?
Make a specific type for localfield, then set that as the type of MyParameter, something along the lines of:
type
TTheArray = array[1..5] of array[1..10] of Integer;
var
LocalField: TTheArray;
constructor TMyClass.Create(MyParameter: TTheArray);
...
(Note: not verified in a compiler, minor errors may be present)
Note that most often in pascal-like syntax a multidimensional array is more properly declared as
type
TTheArray = array[1..5, 1..10] of Integer;
Unless, of course, you have some good reason for doing it the other way.
I don't have Delphi at hands, but I think this should work:
type
TIntArray = array of Integer;
...
constructor TMyClass.Create (MyParameter : array of TIntArray);
begin
...
end;
If a type is used before as suggested in the answer, please note that you are passing it as a reference, see:
https://blog.spreendigital.de/2016/08/01/pass-a-multidimensional-array-as-a-parameter-with-a-hidden-caveat/
I prefer this
procedure MakeMat(var c: TMatrix; nr, nc: integer; a: array of double);
var
i, j: integer;
begin
SetLength(c, nr, nc);
for i := 0 to nr-1 do
for j := 0 to nc-1 do
c[i,j] := a[i*nc + j];
end;
MakeMat(ya, 5, 11,
[1.53,1.38,1.29,1.18,1.06,1.00,1.00,1.06,1.12,1.16,1.18,
0.57,0.52,0.48,0.44,0.40,0.39,0.39,0.40,0.42,0.43,0.44,
0.27,0.25,0.23,0.21,0.19,0.18,0.18,0.19,0.20,0.21,0.21,
0.22,0.20,0.19,0.17,0.15,0.14,0.14,0.15,0.16,0.17,0.17,
0.20,0.18,0.16,0.15,0.14,0.13,0.13,0.14,0.14,0.15,0.15]);

Resources