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;
Related
I read some articles like https://helloacm.com/absolute-keyword-in-delphi/ about the Delphi absolute keyword, but I wish to better understand how to use it.
Could you provide me a real world example about how to use that feature?
When using absolute you can have another variable at the same memory address of a variable that already exists. Optimised excerpt from my previous answer:
var
Number: Integer;
Text: String;
Flag: Boolean;
SameNumber: Integer absolute Number;
OtherText: String absolute Text;
Switch: Boolean absolute Flag;
begin
// Pascal's keyword "absolute" makes the variable merely an alias of
// another variable, so anything you do with one of both also happens
// with the other side.
Number:= 1138; // Same as SameNumber:= 1138;
OtherText:= 'Content'; // Same as Text:= 'Content';
Flag:= TRUE; // Same as Switch:= TRUE;
It also is a way to access a function's result variable when having cascading functions:
function FirstLevel: Boolean;
var
FirstResult: Boolean absolute result;
function SecondLevel: Boolean;
begin
result:= TRUE; // This function
FirstResult:= FALSE; // The outer function
end;
begin
SecondLevel();
// Now this function's result has been set already
end;
And it also can be used to wildly cast between types - it doesn't even have to make sense. But should:
var
Letter: WideChar; // 16 bit long
CodePoint: Word absolute Letter; // also 16 bit long
begin
CodePoint:= $30c4; // Katakana "Tu"
// Letter automatically becomes 'ツ'
Letter:= 'И'; // Cyrillic "I"
// Codepoint automatically becomes $0418
I often use it in this construct:
PROCEDURE LabelClick(Sender : TObject);
VAR
LBL : TLabel ABSOLUTE Sender;
BEGIN
.
. // Use LBL.Caption etc. instead of casting Sender on every use
. // or use an explicit assignment
.
END;
They absolute keyword tells the compiler to create a variable at the same address as another variable.
In the example below, we declare a variable Str of type String[32] (Short string). This variable in memory is represented by a byte whose value is the length of the string and then an array of byte being the ascii code of the characters composing the string. The variable StrLen of type Byte is defined at the same address as the variable Str, the resulting effect is that the value of StrLen is the length of the Str string.
procedure TForm1.Button1Click(Sender: TObject);
var
Str : String[32];
StrLen : Byte absolute Str;
begin
Str := 'ABC';
Memo1.Lines.Add(IntToStr(StrLen)); // Will display "3"
end;
absolute keyword is somewhat an outdated feature. Using it is most of the time a bad idea. Use of a cast is probably better in almost all situations.
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.
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.
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.
I'm trying to store a set inside the object property (and read it) of a TStringList (I will also use it to store text associated to the set) but I get a invalid typecast for the set.
What's the best way to store a set inside a StringList object? Also, will this object need to be freed when destroying the StringList?
Here's some example code:
type
TDummy = (dOne, dTwo, dThree);
TDummySet = set of TDummy;
var
DummySet: TDummySet;
SL: TStringList;
begin
SL := TStringList.Create;
Try
DummySet := [dOne, dThree];
SL.AddObject('some string', TObject(DummySet)); // Doesn't work. Invalid typecast
Finally
SL.Free;
End;
end;
First read the other answers - probably you'll find a less hacky solution.
But FTR: You can write
SL.AddObject('some string', TObject(Byte(DummySet)));
and
DummySet := TDummySet(Byte(SL.Objects[0]));
if you really want.
Note: You'll have to change the keyword Byte if you add enough elements to the TDummySet type. For example, if you add six more elements (so that there is a total of nine) you need to cast to Word.
I can't add non objects on that case.
What you can do, is create an object that have TDummySet as Field.
Something like
TExemple = class
DummySet = TDummySet;
end;
Or you can use a different approach:
Declarations:
TDummy = (dOne, dTwo, dThree);
TDummySet = set of TDummy;
PDummySet = ^TDummySet;
How to use:
var
DummySet: PDummySet;
begin
New(DummySet);
DummySet^ := [dOne, dThree];
You should not store a set via TStringList.Objects because what Objects use (TObject) is a 32 bit value type and sets can be represented up to 256 bits depending on the size of the set. That's probably why the compiler doesn't even allow casting.
A better way to serialize sets is using RTTI. I am not sure where VCL exposes its builtin set serialization mechanism but JCL has a JclRTTI unit with JclSetToStr and JclStrToSet functions.
var
fs: TFontStyles;
begin
JclStrToSet(TypeInfo(TFontStyles), fs, 'fsBold, fsItalic'); // from string
Showessage(JclSetToStr(TypeInfo(TFontStyles), fs)); // to string
end;
I don't think a stringlist is the way to go. Why not an array of TDummySet? And no, there is no need to free it because the set is not an object.
var
Test: Array of TDummySet;
SetLength(Test, 2);
Test[0] := [dOne, dThree];
Test[1] := [dTwo];
When you're done:
SetLength(Test, 0);
You cannot make a typecast from your set to a TObject, because your variable is not a pointer.
You have to store a pointer to your variable in the TStringList. In that case, you'll have to allocate and deallocate it manually too.
Try something like this:
type
TEnum = (one, two, three);
TSet = set of TEnum;
PSet = ^TSet;
var s: TStringList;
p: PSet;
begin
s := TStringList.Create;
p := AllocMem(SizeOf(TSet));
p^ := [two, three];
S.AddObject('a', TObject(p));
// bla bla bla
// Here you read the set in the string list
if (two in PSet(S.Objects[0])^)) then begin
// your checks here
end
...