Pass static byte array in parameter in Delphi - 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.

Related

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.

How to work with Generic methods and parameter array of array of < T >?

A lot of times when working with generic arrays I need to know Max length of the arrays I'm working with. For a longtime I was using:
MaxIntValue([Length(Array1), Length(Array2) , Length(Array3)]); // from Math unit
Then I simplified this (to skip typing Length()) with my method:
// array of array of TCardBrand
function GetMaxLength(const aArrays: array of TArrayOfCarBrand): integer;
Works good, but now I want to create a generic MaxLength and I can't make it work with
class function MaxLength<T>(aArrays: array of array of T): integer; - it gives error: [dcc32 Error] Unit2.pas(31): E2029 Identifier expected but 'ARRAY' found
Here is the code what I'm testing, with working example of MaxIntValue from Math unit and example of my method, but extending generic TArray doesn't work:
TCarBrand = record
BrandID: integer;
BrandName: string;
BrandCountry: string;
end;
TArrayOfCarBrand = array of TCarBrand;
TArray = class(System.Generics.Collections.TArray)
public
// ERROR: E2029 Identifier expected but 'ARRAY' found
class function MaxLength<T>(aArrays: array of array of T): integer;
end;
var
vCarsUS, vCarsEU, vCarsJP: TArrayOfCarBrand;
implementation
{$R *.dfm}
class function TArray.MaxLength<T>(aArrays: array of array of T): integer;
var
i: Integer;
begin
Result := 0;
for i := Low(aArrays) to High(aArrays) do
Result := MaxIntValue([Result, Length(aArrays[i])]);
end;
function GetMaxLength(const aArrays: array of TArrayOfCarBrand): integer;
var
i: Integer;
begin
Result := 0;
for i := Low(aArrays) to High(aArrays) do
Result := MaxIntValue([Result, Length(aArrays[i])]);
end;
procedure TForm2.Button1Click(Sender: TObject);
var vMaxLength: integer;
begin
// test lengths
SetLength(vCarsUS,1);
SetLength(vCarsEU,10);
SetLength(vCarsJP,100);
// using MaxIntValue from Math unit - works OK
vMaxLength := MaxIntValue([Length(vCarsUS), Length(vCarsEU), Length(vCarsJP)]);
// using my method GetMaxLength - works OK
vMaxLength := GetMaxLength([vCarsUS, vCarsEU, vCarsJP]);
// trying to set Generic TArray.MaxLength - ERROR
vMaxLength := TArray.MaxLength<TCarBrand>([vCarsUS, vCarsEU, vCarsJP]);
end;
How can I extend generic MaxValue to accept open array of array of < T >?
It's simple enough. Your function does not accept generic arrays, because the parameter list does not attempt to do so. Change it like this.
class function MaxLength<T>(const aArrays: array of TArray<T>): Integer;
The key here is to use TArray<T> exclusively. This is the generic dynamic array type and using it gives more flexible type compatibility than with array of dynamic array types. The issues are covered in detail here: What are the reasons to use TArray instead of Array of T?
A consequence of this is that you will need to replace all of your dynamic type declarations and use TArray<T> instead. In fact you don't need to declare any dynamic array types at all. You can remove all declarations of this form:
type
TFooArray = array of TFoo;
And then replace all occurrences of TFooArray with TArray<TFoo>. A side benefit is that you don't need to declare any of the array types and your code becomes less verbose.
I would also comment that there's little point in using MaxIntValue when you only have two arguments. Use the Max function then.

How to convert a null terminated string to string?

I want to store to disk some data records. I want to have each data record of equal size so I can quickly compute and jump to a certain record.
So, I store the only string in my record as an array of chars:
type TFileNameIO = array[1..512] of Char;
After I read the string from disk, the content of my string is like this:
c:\windows#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 etc
I want to truncate the #0 characters, and I do something like this:
function GetFileName(DiskName: TFileNameIO): string;
VAR PC: PChar;
begin
SetString(Result, PChar(#DiskName), Length(DiskName)); <----- here Result is: c:\windows#0#0#0 etc
PC:= PChar(Result);
Result:= WideCharToString(PC);
end;
There is a better way to do it?
You can assign a null-terminated PChar directly to a String:
function GetFileName(DiskName: TFileNameIO): string;
begin
Result := PChar(#DiskName);
end;
Just make sure the TDiskName always contains a null terminator. If it can ever be completely full of characters without a null, you will have to do this instead:
function GetFileName(DiskName: TFileNameIO): string;
var
Len, I: Integer;
begin
Len := 0;
For I := Low(DiskName) to High(DiskName) do
begin
if DiskName[I] = #0 then Break;
Inc(Len);
end;
SetString(Result, PChar(#DiskName), Len);
end;
Your function is not necessary because you can simply assign a character array to a string variable.
type
TCharArray = array [1..512] of Char;
....
var
arr: TCharArray;
str: string;
....
arr := ...;
str := arr;
This results in str being assigned the contents of arr. The compiler implements this with a call to System._UStrFromWArray which first of all looks for a null-terminator. If it finds one, that determines the length of str. Otherwise, the entire contents of the array are copied, and Length(str)=Length(arr).
As a broader point, I would recommend that you change to using zero-based character arrays. From the documentation:
Zero-based character arrays are compatible with PChar and PWideChar. When you use a character array in place of a pointer value, the compiler converts the array to a pointer constant whose value corresponds to the address of the first element of the array.
This makes it easier for you to interop with functions that expect PChar arguments.
As an aside, passing TFileNameIO by value is inefficient because it involves a memory copy. Pass such types by reference. For instance as a const parameter.

no RTTI on unamed data types

AFAIK the compiler does not generate RTTI if the type is not named.
eg: T = array[0..1,0..1] of Integer;
In this case it is possible to know the total size of the array, but it is impossible to know the size of each dimension.
It works only if I use a type explicitly named:
T01 = 0..1;
T = array[T01,T01] of Integer;
I missed something?
Test code:
type
t = array[0..1, 0..1] of Integer;
procedure test;
var
i: PTypeInfo;
d: TArrayTypeData;
begin
i := TypeInfo(t);
assert(i.Kind = tkArray);
d := GetTypeData(i).ArrayData;
end;
You can still get array dimensions using builtins High and Low. Let's take the example type array[0..1,3..4] of Integer:
Low(T) // low bound of first range (0)
High(T) // high bound of first range (1)
Low(T[Low(T)]) // low bound of second range (3)
High(T[Low(T)]) // high bound of second range (4)
In the latter two, you can use any valid index in the index value.
Yes this is currently limitation of the RTTI information generated, you must have a type name.
Things like this will not work:
var
StrArray : Array of String;
But the following will work:
type
TStrArray = Array of String;
var
StrArray : TStrArray;
I typically have switched my switched my dynamic arrays to use the new syntax of
TArray which is defined in the system.pas unit as, to make sure they do have names.
TArray<T> = array of T;
So a workaround to your specific problem would be to declare a type name for that array.
type
TMyArray = array[0..1, 0..1] of Integer;
var
t : TMyArray;

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