Delphi TBytes - how to copy? - arrays

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?

Related

Modern Delphi to C strings for passing to C DLLs

I've got some old C DLLs that serve as intermediaries between my Delphi app and MATLAB compiled DLLs. These were originally developed with Delphi 7, and worked fine. Now, however, I've upgraded first to XE7 and now to 10.2.3, and I'm having problems. Basically, I have to pass a pointer to a record to the DLL; the structure includes fields that are pointers to old-fashioned C-style strings (i.e. an array of chars, where each char is a byte, and terminated by a 0 byte).
So consider this record:
MyRec = record
id: Integer;
name: PByte;
end;
I wrote a function like this:
function MakeCString(S: String): TBytes;
var
I: Integer;
len: Integer;
begin
len := Length(S);
SetLength(Result, len + 1);
if len > 0 then
begin
for I := 0 to (len - 1) do
begin
Result[i] := Byte(S[I + 1]);
end;
end;
Result[len] := 0;
end;
Then, my intent is to use this like this:
var r: MyRec;
r.id := 27;
r.name := MakeCString('Jimbo');
callMatlabWrapperDll(#r);
I know this is ugly and prone to memory loss - if it even works reliably at all! It seems to sort of work, but now on a different Windows 10 machine from where it worked (maybe), on my dev machine (also Windows 10), I get errors.
I've banged my head on this long enough. What I need is a way to create a C-style string, pass its address to a DLL, and clean up afterwards. (Note: This is all Win32, running on Win64).
Thanks.
Your MakeCString() is implemented wrong. It is not converting Unicode characters to ANSI, it is just truncating the characters as-is from 16-bit to 8-bit, which is not the same thing.
For that matter, you don't actually need MakeCString() at all. You can use TEncoding.GetBytes() instead.
Also, you are assigning the result of your "conversion" directly to your record's PByte field. You should first assign it to a local TBytes variable, and then get a pointer to its data.
Try this instead:
type
MyRec = record
id: Integer;
name: PByte;
end;
procedure callMatlab;
var
r: MyRec;
name: TBytes;
begin
name := TEncoding.Default.GetBytes('Jimbo'#0);
r.id := 27;
r.name := PByte(name);
callMatlabWrapperDll(#r);
end;
Alternatively, you can (and should) use PAnsiChar instead, which is what C-style APIs generally use for 8-bit strings, eg:
type
MyRec = record
id: Integer;
name: PAnsiChar;
end;
procedure callMatlab;
var
r: MyRec;
name: AnsiString;
begin
name := AnsiString('Jimbo');
r.id := 27;
r.name := PAnsiChar(name);
callMatlabWrapperDll(#r);
end;
This would be more inline with your Delphi 7 code, when String was still AnsiString.
Two things are important to note:
Since Delphi 2009, string is Unicode (that is, two bytes per character).
Delphi strings are essentially C-style strings with an additional header.
The first point means that you must convert the strings from Unicode to ANSI. The second point means that you don't need to write a function that creates a C-style string from a Delphi string -- the Delphi string already is C-style if you just disregard its header. Simply casting a Delphi string to PChar gives you a pointer to the C-style string, because the header is at a negative offset from the string pointer's value.
If MyDelphiString is your Delphi string, this can be converted to an old AnsiString by a simple cast: AnsiString(MyDelphiString). This will create a new string on the heap, with the same content but now in legacy ANSI format (1 byte per char). Of course you will lose "special" characters like ⌬∮☃电脑.
Let us save this to a local variable so we know that its lifetime is (at least) as long as this variable is in scope (thanks Remy Lebeau):
var
MyAnsiString: AnsiString;
begin
MyAnsiString := AnsiString(MyDelphiString)
And to get a pointer to the C-style string which is a subset of this string object, just write PAnsiChar(MyAnsiString). If you insist on using a PByte type in the record, you can be explicit about this reinterpretation as well:
r.Name = PByte(PAnsiChar(MyAnsiString))
But it would be nicer if the record member's type was PAnsiChar instead of PByte.

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.

Inno Setup Acces violation increment array of strings

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;

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.

Is there a clean way to declare a TBytes-compatible value at compile time?

If I wanted to declare a static compile-time array of Byte, I could do it like this:
var
bytes :array[0..24] of Byte = (1, 2, 3, .... );
However, the type of that is array[0..24] of byte, not System.TArray<System.Byte>, which is more commonly known as TBytes.
What I need is something that can be of type TBytes but I don't want to have to add an initialization section to hold these byte values in some painful way:
var
bytes2:TBytes;
initialization
SetLength(bytes2,24);
bytes2[0] := 1; bytes2[1] := 2; ....
Is there some way to do this instead:
var
bytes2:TBytes = (1,2,3, .... );
I tried also to find a way to convert quickly from TBytes (System.TArray<System.Byte>) and array[0..24] of Byte, like this:
bytes2 := byte;
Unfortunately, the closest I can get is this brute-force code:
SetLength(bytes2,Length(bytes));
for n := 0 to Length(bytes) do begin
bytes2[n] := bytes[n];
end;
It seems to me for two types so closely related, that the compiler could do a little better job of allowing me to coerce or copy, from the one type to the other. Anybody else feel that way about various types of "Array of X"? Know any cool ways around it? If the compiler did some magic, it might make the Move(...) function work for this case, but Move actually gives you an access violation, and can't be used with dynamic arrays or generic collections.
How about:
var
bytes: TBytes;
begin
bytes := TBytes.Create(1,2,3, .... );
end;
That said I always find it limiting that this syntax does not accept open arrays. So I have a bunch of functions that look like this:
function Bytes(const A: array of Byte): TBytes;
var
i: Integer;
begin
SetLength(Result, Length(A));
for i := low(Result) to high(Result) do
Result[i] := A[i];
end;
...
var
b1, b2: TBytes;
b3: array of Byte;
b4: array [0..42] of Byte;
...
b1 := Bytes(b2);
b1 := Bytes(b3);
b1 := Bytes(b4);
b1 := Bytes([1,2,3,4]);
I believe that the various generics enhancements in XE mean that this could be done with generics and without duplicating routines like Bytes for each different scalar.

Resources