Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2023-11

Difference between string and Tbytes as procedure arguments?

4 messages · 2 people ·

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Bo Berglund

I am maintaining an old utility program that started out about 20+ years ago using Delphi7.

The utility communicates via RS232 with a measuring system in order to retrieve and process recorded data and at the time this was started I used strings as buffers for the serial comm.

A string at the time was really just an array of 1-byte characters but it had useful functions for manipulating data inside so it made sense to use strings at the time.

But as time evolved and Borland changed the meaning of string to now be unicodestring it could no longer be used as a comm buffer, so I switched declaration to AnsiString or even RawByteString to keep the existing utility tools operational.

New programs I wrote would use TBytes as buffer instead...

Then about 6-7 years ago I had to do a major overhaul of the program in order to improve the user interface and add support for switching languages in the UI and at that time I also tried my best to replace the AnsiString buffers with TBytes buffers. At this time I was on Delphi XE5.

It all looked OK until I recently got a call from a distributor who had discovered that for a certain type of seldom used data collection mode the output was corrupted when transfered to disk....

And now I have found that the corruption happens inside a function that analyzes one of the data packets where it will remove the start header record from the buffer and then continue parsing the data items. (The buffer is sent in as a regular argument without the var specifier).

Following this analysis in main code the buffer itself (that was used in the previous call) is saved to disk in binary format.
And here is the problem:

The saved image of the buffer lacks the header part (30 bytes)...

So my question is this:
Is there a difference in handling the arguments between string, AnsiString, RawByteString and TBytes in a declaration like this:

function TSSConnection.ParseCmdFileData(var SSCmdFile: TSSCommandFile; Buf:
AnsiString): boolean;

and this:

function TSSConnection.ParseCmdFileData(var SSCmdFile: TSSCommandFile; Buf:
TBytes): boolean;

In the first instance it looks like the function receives a *copy* of the data in the Buf argument and in the second case it receives a pointer to the actual live data such that in the first case the argument source remains untouched whereas in the second case the argument source is changed just as it had been declared as var...

Is there a way to simply tell Delphi/FreePascal to treat also TBytes as copy rather than a pointer to the real data?

Bo Berglund Developer in Sweden

Reply by email

Michael Van Canneyt
to Bo Berglund
Bo Berglund wrote 51 lines
I am maintaining an old utility program that started out about 20+ years ago using Delphi7. The utility communicates via RS232 with a measuring system in order to retrieve and process recorded data and at the time this was started I used strings as buffers for the serial comm. A string at the time was really just an array of 1-byte characters but it had useful functions for manipulating data inside so it made sense to use strings at the time. But as time evolved and Borland changed the meaning of string to now be unicodestring it could no longer be used as a comm buffer, so I switched declaration to AnsiString or even RawByteString to keep the existing utility tools operational. New programs I wrote would use TBytes as buffer instead... Then about 6-7 years ago I had to do a major overhaul of the program in order to improve the user interface and add support for switching languages in the UI and at that time I also tried my best to replace the AnsiString buffers with TBytes buffers. At this time I was on Delphi XE5. It all looked OK until I recently got a call from a distributor who had discovered that for a certain type of seldom used data collection mode the output was corrupted when transfered to disk.... And now I have found that the corruption happens inside a function that analyzes one of the data packets where it will remove the start header record from the buffer and then continue parsing the data items. (The buffer is sent in as a regular argument without the var specifier). Following this analysis in main code the buffer itself (that was used in the previous call) is saved to disk in binary format. And here is the problem: The saved image of the buffer lacks the header part (30 bytes)... So my question is this: Is there a difference in handling the arguments between string, AnsiString, RawByteString and TBytes in a declaration like this: function TSSConnection.ParseCmdFileData(var SSCmdFile: TSSCommandFile; Buf: AnsiString): boolean; and this: function TSSConnection.ParseCmdFileData(var SSCmdFile: TSSCommandFile; Buf: TBytes): boolean; In the first instance it looks like the function receives a *copy* of the data

No, but an ansistring is copy-on-write. As soon as you change it, a copy is made if your routine is not the only one using the string.

in the Buf argument and in the second case it receives a pointer to the actual live data such that in the first case the argument source remains untouched whereas in the second case the argument source is changed just as it had been declared as var...

That is correct. TBytes is a fancy wrapper around a pointer with some reference counting added to the mix.

Is there a way to simply tell Delphi/FreePascal to treat also TBytes as copy rather than a pointer to the real data?

You must copy the data. The copy() function can be used for this.

Michael.

Reply by email

Bo Berglund
to Michael Van Canneyt
Michael Van Canneyt wrote 30 lines
> So my question is this: > Is there a difference in handling the arguments between string, AnsiString, > RawByteString and TBytes in a declaration like this: > > function TSSConnection.ParseCmdFileData(var SSCmdFile: TSSCommandFile; Buf: > AnsiString): boolean; > > and this: > > function TSSConnection.ParseCmdFileData(var SSCmdFile: TSSCommandFile; Buf: > TBytes): boolean; > > In the first instance it looks like the function receives a *copy* of the data No, but an ansistring is copy-on-write. As soon as you change it, a copy is made if your routine is not the only one using the string. > in the Buf argument and in the second case it receives a pointer to the actual > live data such that in the first case the argument source remains untouched > whereas in the second case the argument source is changed just as it had been > declared as var... That is correct. TBytes is a fancy wrapper around a pointer with some reference counting added to the mix. > > Is there a way to simply tell Delphi/FreePascal to treat also TBytes as copy > rather than a pointer to the real data? You must copy the data. The copy() function can be used for this.

Follow-up question:

Can I change the function declaration like this and preserve the content?

function TSSConnection.ParseCmdFileData(var SSCmdFile: TSSCommandFile; const
Buf:  TBytes): boolean;

Or must I copy the argument Buf inside the function to a local BufL version? That is what you adviced, right?

Note: I have now found another function that changes the content of the Buf argument and it too causes havoc...

Bo Berglund Developer in Sweden

Reply by email

Michael Van Canneyt
to Bo Berglund
Bo Berglund wrote 9 lines
> > You must copy the data. The copy() function can be used for this. Follow-up question: Can I change the function declaration like this and preserve the content? function TSSConnection.ParseCmdFileData(var SSCmdFile: TSSCommandFile; const Buf: TBytes): boolean;

This does not preserve the content. The "const" means you cannot change the pointer, so

Buf:=a;

will fail

But

Buf[0]:=1;

will still compile.

Or must I copy the argument Buf inside the function to a local BufL version? That is what you adviced, right?

Yes.

Mcihael.

Reply by email