I have a function inside of an unmodifyable DLL that wants a #buffer passed with PBYTE.
function Base(Buffer: PBYTE): Integer;
On top of it I have my buffers with different lengths.
Now I want to create something that works like an Array holding all of these buffers for easier accessibility (an Index is required). I tried turning it every which way, but I'm in a mental dead end.
Buffer1: array[0..100] of Byte;
Buffer2: array[250..600] of Byte;
Buffers: array of PByte;
came closest to working, with the problem on usage in my other functions now trying to accept
function Foo(var Buffer: PByte): Boolean;
begin
Result := Boolean(Buffer[SomePosition]);
end;
throwing
"Array-Type required".
I turned the whole structure upside down interchanging Pointers, Arrays of Pointers introduced with my own types or creating an Array of Array of PByte, which leads to issues with my naturally inconsistent array lengths. I can't solve it.
Edit: The details
buffers : array of PByte;
buffer_DB11 : array[0..200] of byte;
buffer_DB20 : array[400..640] of byte;
function ReadInt(var Buffer : array of Byte; Position : Word): Integer;
begin
Result := buffer[Position]*256+buffer[Position+1];
end;
procedure TServiceItem.FetchValues; // somewhere else
begin
Value := IntToStr( ReadInt( buffers[db], Position) );
end;
I hope that can make clearer what I'm trying to do.
I don't see where the unmodifiable DLL comes into it, so going off your edit you seem to assume that PByte and array of byte are the same thing. They are not.
buffers[ xxx] is of type PByte not array of byte, so either you need to cast or modify your function to take a PByte.
buffers : array of PByte;
buffer_DB11 : array[0..200] of byte;
buffer_DB20 : array[400..640] of byte;
function ReadInt(var Buffer : PByte; Position : Word): Integer;
begin
Result := buffer[Position]*256+buffer[Position+1];
end;
procedure TServiceItem.FetchValues; // somewhere else
begin
Value := IntToStr( ReadInt( buffers[db], Position) );
end;
Related
I'm using Delphi 2007 (Pre generics) and I've defined many functions who can be used for all arrays of TObject's descendants, example:
function IndexOf(AArray : array of TObject; AItem : TObject) : integer;
begin
//...
end;
For passing them dynamic arrays of TObject's descendants, I've defined an array type TObjectArray = array of TObject. In this way I can cast dynamic arrays and pass them to my functions without any problems:
type
TChild = class(TObject);
...
procedure Test();
var
Items : array of TChild;
Item : TChild;
begin
//...
IndexOf(TObjectArray(Items), Item);
end;
The problem comes when I try to pass them open array parameters:
procedure Test(AItems : array of TChild);
var
Item : TChild;
begin
//...
IndexOf(TObjectArray(AItems), Item);
end;
In these cases, the compiler raises the following error message:
E2089 Invalid typecast
Why does this happen and how can I avoid that?
You don't need to typecast when passing ANY type of array to an open array parameter, provided the elements are the same type. You can pass the array as-is, the open array will accept it just fine. That is the whole point of open arrays.
type
TChild = class(TObject);
...
function IndexOf(AArray : array of TObject; AItem : TObject) : integer;
begin
//...
end;
procedure Test();
var
Items : array of TObject;
Item : TChild;
begin
//...
IndexOf(Items, Item);
end;
procedure Test2();
var
Items : array[0..N] of TObject;
Item : TChild;
begin
//...
IndexOf(Items, Item);
end;
procedure Test3(AItems : array of TObject);
var
Item : TChild;
begin
//...
IndexOf(AItems, Item);
end;
However, you cannot pass an array of TChild where an array of TObject is expected. The compiler will reject it with an "incompatible types" error. The input array must use the same element type as the open array.
A simple typecast can fix that when passing a dynamic array or a fixed array:
procedure Test();
type
TObjectArray = array of TObject;
var
Items : array of TChild;
Item : TChild;
begin
//...
IndexOf(TObjectArray(Items), Item);
end;
procedure Test2();
type
TObjectFixedArray = array[0..N] of TObject;
PObjectFixedArray = ^TObjectFixedArray;
var
Items : array[0..N] of TChild;
Item : TChild;
begin
//...
IndexOf(PObjectFixedArray(#Items)^, Item);
end;
But, you simply cannot typecast an open array to any other array type. Different types of arrays have different memory layouts (typecasting a dynamic array to another dynamic array, or a fixed array to another fixed array, does not change the memory layout of the array being typecasted).
In the case of an open array, it is actually not even an array at all, it is just a pointer to the first element of the passed array, and there is a second hidden parameter for the array length. In other words, this kind of declaration:
procedure Test3(AItems : array of TChild);
Is actually implemented by the compiler behind the scenes like this:
procedure Test3(AItems : ^TChild; AItems_High: Integer);
So, you will have to make a copy of the open array elements to another array, and then pass that array along instead:
procedure Test3(AItems : array of TChild);
var
Items: array of TObject;
Item : TChild;
I: Integer;
begin
//...
SetLength(Items, Length(AItems));
For I := Low(AItems) to High(AItems) do
Items[I] := AItems[I];
IndexOf(Items, Item);
end;
I need to call a function expecting an array of Integer, but I have my values in a variable of type Variant, containing the array.
Do I really have to copy the values in a loop? I couldn't find a better way that works.
The same variant can also hold a single Integer instead of the array, so I created a helper function allowing both (checking with VarIsArray). It works, but it is just lengthy and not nice :)
type
TIntegerArray = array of Integer;
function VarToArrayInt(const V: Variant): TIntegerArray;
var
I: Integer;
begin
if VarIsArray(V) then begin
SetLength(Result, VarArrayHighBound(V, 1) + 1);
for I:= 0 to High(Result) do Result[I]:= V[I];
end else begin
SetLength(Result, 1);
Result[0]:= V;
end;
end;
I'm using Delphi 10.2.2 and the function to be called cannot be changed and looks like this:
function Work(Otherparameters; const AParams: array of Integer): Boolean;
If the function takes an array of Integer as a separate type, eg:
type
TIntegerArray = array of Integer;
function DoIt(const Values: TIntegerArray): ReturnType;
Then the function takes a Dynamic Array as input. You can assign/pass a Variant holding an array to a Dynamic Array variable/parameter. The compiler is smart enough to call the RTL's VarToDynArray() function to allocate a new Dynamic Array that has a copy of the Variant's array elements. There is no way to pass a Variant holding an array to a Dynamic Array without making a copy of the array data.
However, if the function takes an array of Integer directly in its parameter list instead, eg:
function DoIt(const Values: array of Integer): ReturnType;
Then it takes an Open Array as input:
an Delphi function that has an open array parameter can be called by explicitly passing two parameters:
A pointer to the first element of the array
A count, which is the value of the last index (that is, the size/number of array elements, minus one)"
You can't pass a Variant (whether it holds an array or not) directly to an Open Array parameter. The compiler is not smart enough to extract the array pointer and element count and pass them to the Open Array parameter. However, you can do it manually with a little typecast trickery, eg:
function DoIt(const Values: array of Integer): ReturnType;
...
type
TOpenArrayFunc = function(const Values: PInteger; ValuesHigh: Integer): ReturnType;
var
V: Variant;
Count: Integer;
P: PInteger;
begin
...
V := ...;
Count := VarArrayHighBound(V, 1) - VarArrayLowBound(V, 1) + 1;
P := VarArrayLock(V);
try
TOpenArrayFunc(#DoIt)(P, Count-1);
finally
VarArrayUnlock(V);
end;
...
end;
This passes the Variant's array directly to the function without making any copies of the array elements at all.
Fortunately there is no need for a loop, at least when the array is 0-based.
If the called function would expect a dynamic array, you could just pass the Variant as it is. You can also directly assign it to a dynamic array variable.
In your case it is an open array parameter, and that needs casting in this case.
Here is some demonstration of what is possible and how, including a nice and short helper function allowing both arrays and single values.
program Test;
uses Variants;
procedure PrintOpenArray(const Arr: array of Integer); {open array parameter}
var
I: Integer;
begin
for I in Arr do Writeln(I);
end;
procedure PrintDynamicArray(const Arr: TArray<Integer>); {dynamic array param}
begin
PrintOpenArray(Arr);
end;
function VarToArrayInt(const V: Variant): TArray<Integer>;
begin
if VarIsArray(V) then Result:= V else Result:= [V];
{[V] works only in XE7 and up. You can use TArray<Integer>.Create(V) instead}
end;
type {dynamic integer array, but only compatible to this type}
TIntegerArray = array of Integer;
var
V: Variant;
A: TArray<Integer>; {dynamic array, compatible to any other TArray<Integer>}
begin {all the following only works with 0-based arrays!}
V:= VarArrayCreate([0, 2], varInteger);
V[0]:= 1;
V[1]:= 2;
V[2]:= 3;
A:= V; {Variant can just be assigned to dynamic array if it contains an array}
PrintOpenArray(A);
PrintDynamicArray(V); {works directly without casting}
PrintOpenArray(TArray<Integer>(V)); {not possible without casting}
PrintOpenArray(TIntegerArray(V));
PrintOpenArray(VarToArrayInt(V));
V:= 4; {demonstration of helper function to allow arrays and single values}
PrintOpenArray(VarToArrayInt(V));
PrintDynamicArray(VarToArrayInt(V));
Readln;
end.
I have an array of bytes and another array of array of bytes.
type
TByteArray = array of byte;
TArraykearray = array of array of byte;
function TFRTConnection1.GetBytes(value: integer): TBytearray;
begin
SetLength(Result, SizeOf(value));
Move(value, Result[0], SizeOf(value));
end;
Function TFRTConnection1.addco(point: TPoint) : Tarraykearray;
var
result1 : Tarraykearray;
begin
setLength(Result1,10);
Result1[0] := getBytes(1);
Result1[1] := 1;
....
Result := result1;
end;
When i use Result1[0] := getBytes(1); i get incompatible types error.
I do not want to make TArraykearray as array of Tbytearray because that is an array of array on the server side of the application for which I do not have access to.
Is there another possibility?
You cannot assign a TByteArray to an array of byte, the compiler treats them as separate types even though internally they are compatible. You need to change TArraykearray to use TByteArray instead:
type
TByteArray = array of byte;
TArraykearray = array of TByteArray;
Or else you will have to use a type-cast when assigning the array:
Function TFRTConnection1.addco(point: TPoint) : Tarraykearray;
type
PByteArray = ^TByteArray;
var
Result1 : TArraykearray;
begin
SetLength(Result1,10);
PByteArray(#Result1[0])^ := getBytes(1);
....
end;
BTW: Result1[1] := 1; will not compile either, as you cannot assign a single integer to an array.
I'm trying to implement array append in Delphi 7, because the standard library does not have it. This is what I have so far, but it complains about incompatible types when I try to get the length of the array.
procedure ArrayAppend(var GenericArray; const Element);
var
len: Integer;
begin
len := Length(GenericArray);
SetLength(GenericArray, len+1);
GenericArray[len] := Element;
end;
I'm trying to find what the most generic type of array is in Delphi 7.
I also have no issue returning the modified array if that's the only way to do it.
To illustrate what Rob Kennedy said in comments, here is a function that will append a value to a dynamic array. However, this is a rather crippled function in that it does not support managed types. So you cannot have strings, interfaces, variants or dynamic arrays. Or indeed compound structures that contain any managed types.
{$APPTYPE CONSOLE}
uses
SysUtils,
TypInfo;
type
PDynArrayTypeInfo = ^TDynArrayTypeInfo;
TDynArrayTypeInfo = packed record
kind: Byte;
name: Byte;
elSize: Longint;
elType: ^PDynArrayTypeInfo;
varType: Integer;
end;
function DynArraySize(a: Pointer): Integer;
asm
TEST EAX, EAX
JZ ##exit
MOV EAX, [EAX-4]
##exit:
end;
type
TIntegerArray = array of Integer;
procedure AppendUnmanaged(var arr{: TArray<T>}; const Value{: T}; TypeInfo: PTypeInfo);
var
len, elSize: Integer;
begin
len := DynArraySize(Pointer(arr)) + 1;
DynArraySetLength(Pointer(arr), TypeInfo, 1, #len);
inc(PAnsiChar(TypeInfo), PDynArrayTypeInfo(TypeInfo).name);
elSize := PDynArrayTypeInfo(TypeInfo).elSize;
Move(Value, (PAnsiChar(Pointer(arr)) + (len-1)*elSize)^, elSize);
end;
procedure Main;
var
arr: TIntegerArray;
i, Value: Integer;
begin
Value := -1;
AppendUnmanaged(arr, Value, TypeInfo(TIntegerArray));
Value := 666;
AppendUnmanaged(arr, Value, TypeInfo(TIntegerArray));
Value := 42;
AppendUnmanaged(arr, Value, TypeInfo(TIntegerArray));
for i := low(arr) to high(arr) do
Writeln(arr[i]);
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
You can see some obvious problems with this when compared with modern day Delphi which has generic types and array concatenation baked into the language. Specifically I'm thinking of these issues:
You have to provide a type info to the function.
You cannot pass literals because they are not valid as untyped parameters.
Now, it is certainly possible to replicate what the compiler does when assigning a managed type. But is it really worthwhile? Is the code above, especially the calling code, really an improvement over the type safe alternatives? Personally I don't think so.
I have a generic list of records. these records contains a dynamic array like following
Type
TMyRec=record
MyArr:Array of Integer;
Name: string;
Completed: Boolean;
end;
var
MyList:TList<TMyRec>;
MyRec:TMyRec;
then I create the list and set the array length like followings
MyList:=TList<TMyRec>.Create;
SetLength(MyRec.MyArr,5);
MyRec.MyArr[0]:=8; // just for demonstration
MyRec.Name:='Record 1';
MyRec.Completed:=true;
MyList.Add(MyRec);
then i change the data in MyArr and i also change MyRec.Name and add another item to the list
MyRec.MyArr[0]:=5; // just for demonstration
MyRec.Name:='Record 2';
MyRec.Completed:=false;
MyList.Add(MyRec);
when MyRec.MyArr changes after adding the first item to the list, MyArr which is stored to the list also changes. however the other record fields does not.
My question is how to prevent the changes in MyRec.MyArr to be reflected on the array which is already stored in the list item.
do i need to declare multiple records.
This example can be simplified like so, removing all reference to generics:
{$APPTYPE CONSOLE}
var
x, y: array of Integer;
begin
SetLength(x, 1);
x[0] := 42;
y := x;
Writeln(x[0]);
y[0] := 666;
Writeln(x[0]);
end.
The output is:
42
666
The reason for this is that a dynamic array is a reference type. When you assign to a variable of dynamic array type, you are taking another reference and not making a copy.
You can resolve this by forcing a reference to be unique (that is have just a simple reference). There are a number of ways to achieve this. For instance, you can call SetLength on the array that you want to be unique.
{$APPTYPE CONSOLE}
var
x, y: array of Integer;
begin
SetLength(x, 1);
x[0] := 42;
y := x;
SetLength(y, Length(y));
Writeln(x[0]);
y[0] := 666;
Writeln(x[0]);
end.
Output:
42
42
So, in your code you can write it like this:
MyList:=TList<TMyRec>.Create;
SetLength(MyRec.MyArr,5);
MyRec.MyArr[0]:=8; // just for demonstration
MyRec.Name:='Record 1';
MyRec.Completed:=true;
MyList.Add(MyRec);
SetLength(MyRec.MyArr,5); // <-- make the array unique
MyRec.MyArr[0]:=5; // just for demonstration
MyRec.Name:='Record 2';
MyRec.Completed:=false;
MyList.Add(MyRec);
You can use a variety of other ways to enforce uniqueness, including Finalize, assigning nil, Copy, etc.
This issue is covered in some detail in the documentation. Here are the pertinent excerpts:
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. For example, after
this code executes:
var
A, B: array of Integer;
begin
SetLength(A, 1);
A[0] := 1;
B := A;
B[0] := 2;
end;
the value of A[0] is 2. (If A and B were static arrays, A[0] would
still be 1.) Assigning to a dynamic-array index (for example,
MyFlexibleArray[2] := 7) does not reallocate the array. Out-of-range
indexes are not reported at compile time. In contrast, to make an
independent copy of a dynamic array, you must use the global Copy
function:
var
A, B: array of Integer;
begin
SetLength(A, 1);
A[0] := 1;
B := Copy(A);
B[0] := 2; { B[0] <> A[0] }
end;
...here were observations of the original question controverscies
What for the rest, i'd prefer to break the link between your variable and the list immediately after you put added the value. In few months you would forgot that issue you had and maybe would refactor your program. If you put the 2nd SetLength away from List.Add you may just forget that the record still holds a reference to the same array you have in the list.
TMyRec=record
MyArr: TArray< double >; // making it 1D for simplicity
Name: string;
Completed: Boolean;
end;
SetLength(MyRec.MyArr,5);
MyRec.MyArr[0]:=8; // just for demonstration
MyRec.Name:='Record 1';
MyRec.Completed:=true;
MyList.Add(MyRec);
MyRec.MyArr := nil; // breaking the parasite link immediately!
...now here you can do anything you want - but MyRec is already clean.
Then, what if you would have many arrays, not just one ? There is one function that Delphi uses itself behind the curtains: http://docwiki.embarcadero.com/Libraries/XE5/en/System.Finalize which would have find all the arrays to clean.
SetLength(MyRec.MyArr,5);
MyRec.MyArr[0]:=8; // just for demonstration
MyRec.Name:='Record 1';
MyRec.Completed:=true;
MyList.Add(MyRec);
Finalyze(MyRec); // breaking all the parasite links immediately!
Now, the last option would just to compact the used code into a procedure, that you would be able to call several times. Then the variable would become local one and Delphi would Finalize it for you automatically.
Procedure AddRec( const Name: string; const Compl: boolean; const Data: array of double);
var i: integer; MyRec: TMyRec;
begin
SetLength(MyRec.MyArr, Length( Data ) );
for i := 0 to Length(Data) - 1 do
MyRec.MyArr[i] := Data [i];
MyRec.Name := Name;
MyRec.Completed := Compl;
MyList.Add(MyRec);
end;
MyList:=TMyList<TMyRec>.create;
AddRec( 'Record 1', True , [ 8 ]);
AddRec( 'Record 2', False, [ 5 ]);
...
Since MyRec is now a local variable, that gets destroyed when exiting from AddRec it would not hold that link to the array and would not conduse neither you nor any other fellow developer, who would use your types.
Just create a new one into old variable, every thing should be Ok,
MyList:=TList<TMyRec>.Create;
SetLength(MyRec.MyArr,5);
MyRec.MyArr[0]:=8; // just for demonstration
MyRec.Name:='Record 1';
MyRec.Completed:=true;
MyList.Add(MyRec);
MyRec := TMyRec.Create();
SetLength(MyRec.MyArr,5);
MyRec.MyArr[0]:=5; // just for demonstration
MyRec.Name:='Record 2';
MyRec.Completed:=false;
MyList.Add(MyRec);