Inno Setup Acces violation increment array of strings - arrays

I hava an array of strings. I increment this array when it is necessary with the function:
function insertMessageAction(list: TMessagesActions; message: String): TMessagesActions;
var
lenght: integer;
begin
if message <> '' then begin
lenght := GetArrayLength(list);
SetArrayLength(list, lenght +1);
if GetArrayLength(list) > lenght then begin
list[lenght] := message
end;
end;
result := list;
end;
If de increment is of 0 to 2, no problems, by when I increment to 3 lenght, the array is corrupted and 'value' of list is: "Acces violation at address 00403498. Read of address 0000006A".
It is impossible to create more long arrays of 2 items (strings)? There is limit of characters?
Thanks.

First of all, do not use array for your task. Memory reallocation that happens when changing the size of your string array is an expensive operation. What's more, you were trying to return a copy of that input array (in a wrong way), which would be unecessarily inefficient too.
I strongly suggest you to use the TStringList which is intended to be used for a collection of strings. By using TStringList, the whole code from your question would become:
StringList.Add(Message);
But to your question. It is possible, but you need to comply with a few things; at least:
Do not attempt to return an input array passed by reference:
Result := list;
If you need to have an array as a return type for some reason, allocate the size of this output array and copy all the elements from the input array:
InputLen := GetArrayLength(List);
SetArrayLength(Result, InputLen + 1);
for I := 0 to InputLen - 1 do
Result[I] := List[I];
Result[InputLen] := Message;
If you'd still like to stay by array, better pass it by variable parameter:
[Code]
type
TMessagesActions = TArrayOfString;
procedure InsertMessageAction(var AList: TMessagesActions;
const AMessage: String);
var
ArrayLen: Integer;
begin
ArrayLen := GetArrayLength(AList);
SetArrayLength(AList, ArrayLen + 1);
AList[ArrayLen] := AMessage;
end;

Related

How strings differ from massive(array) of chars in Pascal?

I have a problem. Why I can't assign the value of strings to strings, but with chars it works. Why ^^^^^^? Where's string? Why There's the a[i]?
Is it because internal representation of strings and chars?
program massive.pas;
type
chars = array [1..255] of char;
var
s,s1: string;
ch1,ch2: chars;
i: integer;
begin
s1 := '';
s := 'abrakadabra';
for i := 1 to 5 do
begin
s1[i] := s[i];
writeln(s1[i],#10,'^^^',s1,'^^^')
end;
ch2 := '';
ch1 := 'abrakadabra';
for i := 1 to 5 do
begin
ch2[i] := ch1[i]
writeln(ch2[i])
end;
writeln('%%%',ch2,'%%%');
for i := 1 to 5 do
writeln('&&&',s1[i],'&&&');
end.
*Output
a
^^^^^^
b
^^^^^^
r
^^^^^^
a
^^^^^^
k
^^^^^^
a
b
r
a
k
%%%abrak%%%
&&&a&&&
&&&b&&&
&&&r&&&
&&&a&&&
&&&k&&&
The main difference between type chars = Array[1..255] of Char and String is that a chars array has a fixed length, while the string has a dynamic length.
You did not say which compiler you use, but I do think that the String type is what in some Pascal editions is called a ShortString, with a max length of 255 chars. The space for 255 chars is preallocated and the structure includes a length field, that keeps track of assigned length of the string.
In your example, you assign s1 := ''; in other words the length is set to zero. Then you do a mistake in the for loop in assigning s1[i] := s[i]; without setting the length of s1.
Subsequent reading of s1 always return an empty string as the length field is 0.
If you would assign the characters to the string, e.g. as:
for i := 1 to 5 do
begin
SetLength(s1, Length(s1)+1);
s1[i] := s[i];
writeln(s1[i],#10,'^^^',s1,'^^^');
end;
then the result would be what you originally expected.
Still better to set the length to the final 5 before the for loop.
Of course there are other solutions too. One is to not set the length at all, but to concatenate the string in the loop and let it handle the length field by itself:
for i := 1 to 5 do
begin
s1 := s1 + s[i];
writeln(s1[i],#10,'^^^',s1,'^^^');
end;
Edit 24.12.2021:
In a comment you said: But I still don't understand, why, when i wrote s1[1] in the for loop, all worked?
...and presumably refer to this code just before end.:
for i := 1 to 5 do
writeln('&&&',s1[i],'&&&');
We need to look at the memory layout and know that the first byte of the memory allocated to s1 is the length of the string. It can be referred to as s[0]. Subsequent bytes hold the characters that make up the stored string and they can be referred to as s[1]..s[n].
The first byte was set to 0 when you wrote (in the very beginning):
s1 := '';
// memory content:
0
|_|_|_|_|_|_|_|_|_| ...
Then you added the characters to s1 by manipulating the memory directly when you wrote in the first for loop:
s1[i] := s[i];
// content after 5 characters
0 a b r a k
|_|_|_|_|_|_|_|_|_| ...
Because you did not use concatenation (or adjusted the length while you added the characters) the length is still 0.
Then at the end in the last loop, you fetch the characters again by accessing the memory directly, and get the result you do, seemingly correct, but badly misusing the string structure.

string to array in pascal

Hi I am passing string from command line line // - 2,3,4,5,6 and as a param in ip1.
when I run this code its give error "Error: Type identifier expected " and " Fatal: Syntax error, ";" expected but "ARRAY" found".
Please let me know what is the problem.....
program main;
uses SysUtils;
var
output : Array of integer;
var
ip1 : Array of integer;
function add(input1:Array of integer) : Array of integer;
begin
add := input1;
end;
type
TIntegerArray = Array of Integer;
function IntArray(var input:string) : TIntegerArray;
var
p: integer;
begin
p := Pos(',', input);
if p = 0 then
p := MaxInt - 1;
result[0] := Copy(input, 1, p - 1);
result[1] := Copy(input, p + 1);
end;
begin
ip1 := IntArray(ParamStr(1));
output := add(ip1);
write('output ',output,'time',0.0 );
end.
You have so many problems in the code you've posted, it's difficult to know where to start...
You need to move the type declaration for TIntegerArray up closer to the start of your program, so it can be used as the return type of both add and IntArray, as well as the argument to add. array of Integer and TIntegerArray are two different types in Pascal, and can't be interchanged.
You don't check to see if you received any parameters before blindly using them. If they don't exist, your code doesn't work at all. You need to check to make sure you've received the parameters, and produce a useful message with instructions if you don't find them.
You never allocate any space for the IntArray return value. You need to use SetLength to declare the proper number of elements in the array before you can assign anything to them when using dynamic arrays. (See #4 below.)
Your IntArray just presumes there are only two items in input, where your sample command line shows more. You need to use a loop. (
Your IntArray tries to use ParamStr(1) as a var parameter. ParamStr(1) is a constant, and can't be passed as a var anything.
You can't pass an array to write or writeln directly. You have to loop through the elements in the array and output each individually.
(Not really a problem, just info) add does nothing to "add" anything, so it is really poorly named. You should pick names that actually describe what it's doing so that your code is easier to read and understand. (I'm not sure what you intended to do with add, but what you have now does nothing useful.
(Another "not really a problem", but info.) You don't handle any exceptions in case the parameters are not able to be converted to integers. An invalid value provided to StrToInt will raise an exception. You should either use Val or StrToIntDef, or at the very least use a try..except block around the conversion to handle invalid parameters.
(Another "not really a problem".) You don't do anything to pause the program at the end so you can see the output of the write statement, which makes it very hard to test or debug your program from the IDE.
Here's a working (tested) version of your code.
program main;
uses
System.SysUtils;
type
TIntegerArray = Array of Integer;
var
ip1, output: TIntegerArray;
function add(input1: TIntegerArray) : TIntegerArray;
begin
Result := input1;
end;
function IntArray(input:string) : TIntegerArray;
var
p: Integer;
i: Integer; // Tracks current index into Result array
begin
i := 0;
p := Pos(',', input);
while P > 0 do
begin
Inc(i); // Increment index
SetLength(Result, i); // Allocate element in array
Result[i] := StrToInt(Copy(input, 1, P - 1)); // Assign value
System.Delete(input, 1, P); // Remove portion we just read
P := Pos(',', input); // See if there's another comma
end;
// Now get the part after last ',' and add to array also
i := Length(Result);
if (i > 0) and (input <> '') then
begin
SetLength(Result, i + 1);
Result[i + 1] := StrToInt(input);
Input := '';
end;
end;
var
Ctr: Integer;
begin
if ParamCount > 0 then
begin
ip1 := IntArray(ParamStr(1));
output := add(ip1);
Write('Output: ');
for Ctr := Low(output) to High(output) do
Write(output[Ctr], ' ');
// Don't know what this is supposed to do, but...
WriteLn('time', 0.0 );
end
else
begin
WriteLn('ParamCount: ', ParamCount);
WriteLn('Syntax: ', ExtractFileName(ParamStr(0)) + ' <arg,arg[,arg...]>');
end;
ReadLn;
end.
You need to use tintegerarray as return type for add() too, just like you already do for intarray.
After that you will find out that Pascal is strong typed, and doesn't allow assigning strings to parameters.
The ip1:=intarray(paramstr(1)); typecast looks extremely dodgy btw. Maybe lookup the help for paramstr and paramcount again.

How to make dynamic array constant?

There is any way of making x array constant after the data is read from user? There is any way of making variable not modifiable after it's value is read from user (eg. y)?
program hmm;
uses crt;
var
i, y: word;
x: array of word;
begin
readln(y);
y:=y-1;
SetLength(x,y);
for i := 0 to y do begin
read(x[i]);
end;
readkey;
end.
To make y constant I tried something like this, but it won't work - y will be set as 0.
program hmm;
uses crt;
var
i: word;
x: array of word;
const
{$J+}
y:word = 0;
{$J-}
begin
{$J+}
readln(y);
y:=y-1;
{$J-}
y:=0;
SetLength(x,y);
for i := 0 to y do begin
read(x[i]);
end;
readkey;
end.
Thanks for help.
Yes. Don't change either of them in your code after you set the initial value.
Other than that, there's no way. A dynamic array by definition is changeable, and so is a variable - that's why they have dynamic and variable as names.

How to merge 2 string array in Delphi

I have 2 or more dynamic string array that fill with some huge data , i want to merge this 2 array to one array , i know i can do it with a for loop like this :
var
Arr1, Arr2, MergedArr: Array of string;
I: Integer;
begin
// Arr1:= 5000000 records
// Arr2:= 5000000 records
// Fill MergedArr by Arr1
MergedArr:= Arr1;
// Set length of MergedArr to length of ( Arra1 + Arr2 )+ 2
SetLength(MergedArr, High(Arr1)+ High(Arr2)+2);
// Add Arr2 to MergedArr
for I := Low(Arr2)+1 to High(Arr2)+1 do
MergedArr[High(Arr1)+ i]:= Arr2[i-1];
end;
but it is slow on huge data , is there faster way like copy array memory data ?
First of all string is special, so it should be treated specially: Don't try outsmarting the compiler, keep your code unchanged. String is special because it's reference counted. Every time you copy a string from one place to an other it's reference count is incremented. When the reference count reaches 0, the string is destroyed. Your code plays nice because it lets the compiler know what you're doing, and in turn the compiler gets the chance to properly increment all reference counts.
Sure, you can play all sorts of tricks as suggested in the comments to gabr's answer, like filling the old arrays with zero's so the reference count in the new array remains valid, but you can't do that if you actually need the old arrays as well. And this is a bit of a hack (albeit one that will probably be valid for the foreseeable future). (and to be noted, I actually like this hack).
Anyway, and this is the important part of my answer, your code is most likely not slow in the copying of the strings from one array to the other, it's most likely slowly somewhere else. Here's a short console application that creates two arrays, each with 5M random strings, then merges the two arrays into a third and displays the time it took to create the merge. Merging only takes about 300 milliseconds on my machine. Filling the array takes much longer, but I'm not timing that:
program Project26;
{$APPTYPE CONSOLE}
uses SysUtils, Windows;
var a, b, c: array of string;
i: Integer;
Freq: Int64;
Start, Stop: Int64;
Ticks: Cardinal;
const count = 5000000;
begin
SetLength(a,count);
SetLength(b,count);
for i:=0 to count-1 do
begin
a[i] := IntToStr(Random(1));
b[i] := IntToStr(Random(1));
end;
WriteLn('Moving');
QueryPerformanceFrequency(Freq);
QueryPerformanceCounter(Start);
SetLength(c, Length(a) + Length(b));
for i:=0 to High(a) do
c[i] := a[i];
for i:=0 to High(b) do
c[i+Length(a)] := b[i];
QueryPerformanceCounter(Stop);
WriteLn((Stop - Start) div (Freq div 1000), ' milliseconds');
ReadLn;
end.
You can use built-in Move function which moves a block of memory to another location. Parameters are source and target memory blocks and size of data to be moved.
Because you are copying strings, source arrays must be destroyed after the merging by filling them with zeroes. Otherwise refcounts for strings will be all wrong causing havoc and destruction later in the program.
var
Arr1, Arr2, MergedArr: Array of string;
I: Integer;
begin
SetLength(Arr1, 5000000);
for I := Low(Arr1) to High(Arr1) do
Arr1[I] := IntToStr(I);
SetLength(Arr2, 5000000);
for I := Low(Arr2) to High(Arr2) do
Arr2[I] := IntToStr(I);
// Set length of MergedArr to length of ( Arra1 + Arr2 )+ 2
SetLength(MergedArr, High(Arr1)+ High(Arr2)+2);
// Add Arr1 to MergedArr
Move(Arr1[Low(Arr1)], MergedArr[Low(MergedArr)], Length(Arr1)*SizeOf(Arr1[0]));
// Add Arr2 to MergedArr
Move(Arr2[Low(Arr2)], MergedArr[High(Arr1)+1], Length(Arr2)*SizeOf(Arr2[0]));
// Cleanup Arr1 and Arr2 without touching string refcount.
FillChar(Arr1[Low(Arr1)], Length(Arr1)*SizeOf(Arr1[0]), 0);
FillChar(Arr2[Low(Arr2)], Length(Arr2)*SizeOf(Arr2[0]), 0);
// Test
for I := Low(Arr1) to High(Arr1) do begin
Assert(MergedArr[I] = IntToStr(I));
Assert(MergedArr[I] = MergedArr[Length(Arr1) + I]);
end;
// Clear the array to see if something is wrong with refcounts
for I := Low(MergedArr) to High(MergedArr) do
MergedArr[I] := '';
end;
An excellent maxim is that the fastest code is that which never runs. Since copying is expensive you should look to avoid the cost of copying.
You can do this with a virtual array. Create a class which holds an array of array of string. In your example the outer array would hold two string arrays.
Add a Count property that returns the total number of strings in all of the arrays.
Add a default indexed property that operates by working out which of the outer arrays the index refers to and then returns the appropriate value from the inner array.
For extra points implement an enumerator to make for in work.

Delphi TBytes - how to copy?

Point is opimization here.
Now:
type TSomeClass=class(TObject)
private
DataWrite: TBytes;
...
end;
Function TSomeClass.GetPacket: TBytes;
begin
SetLength(Result, Length(DataWrite));
Move(DataWrite[0],Result[0],Length(DataWrite));
end;
What I want to achieve:
Function TSomeClass.GetPacket: TBytes;
begin
Result := DataWrite;
end;
Because Arrays in Delphi are pointers to first element, the latter only and only writes 4 bytes so it is MUCH faster. Is this correct?
The one thing you need to be aware of is that different from strings, dynamic arrays are not "copy-on-write".
If you assign a string, or a dynamic array, only the pointer to the data on the heap is copied and the reference count is incremented.
But with a string, if you then write into a string (e.g. s[1] := 'a') which has a reference count > 1, the compiler will emit code which makes sure that the string is copied first. This is not the case with dynamic arrays:
var
s, t: string;
a, b: TBytes;
begin
s := 'abc';
t := s;
t[2] := 'X';
WriteLn(s); //still abc
a := TBytes.Create(1, 2, 3);
b := a;
b[1] := 0;
WriteLn(a[1]); // is now 0 not 2!
So in case of your code, if you change the contents of DataWrite after GetPacket was called, the change will be visible in the TBytes that GetPacket returned.
For the code where you actually make a copy of the array, instead of calling SetLength And Move, you can use:
function TSomeClass.GetPacket: TBytes;
begin
Result := Copy(DataWrite, 0, High(Integer));
end;
That will work but note that you are now working on the same byte array in client code that calls GetPacket. This might be a bad idea. Consider some network library that does some additional compression or encryption on the byte array. This creates a lot of possibilites to interact with your class without using the exposed interface - which is bad. Thus IMHO copying is the better option here.
BTW: How big are the arrays we are talking about here?

Resources