Trying to move Delphi 2007 project to XE4. In Delphi 2007 I was using function that reads byte array from socket using Indy directly. I passed AnsiString casted to array of byte to var parameter of this function:

var data:AnsiString;
AContext.Connection.IOHandler.ReadBytes(TIDBytes(Data), PacketLength-PacketLengthDelta-1, False);

In Dlphi XE when I try to concatinate Data to another string I got access violation error.

Now I'm trying to simulate this problem in more simple code:

TIdBytes = array of Byte;

procedure fill(var b: TIDBytes);
begin
setlength(b,5);
b[0]:=61;
b[1]:=61;
b[2]:=61;
b[3]:=61;
b[4]:=61;

//original function used move function
end;



procedure TMainForm.FormCreate(Sender: TObject);
var s: ansistring ;
begin
fill( TIDBytes(s) );
Showmessage(s);
end;

Now I expect to see something like ==== in message box, but I got empty one. I supposed that XE AnsiString acts the same like Delphi 2007 Ansistring and you can use them like byte array in both cases.

What is the best way to solve filling AnsiString with bytes problem?

有帮助吗?

解决方案

It was never valid to cast an AnsiString to a byte array. That code was always broken and you got lucky (or unlucky depending on your point of view).

Managed string types, just like dynamic arrays, have an extra payload of information, metadata, that is stored just before the data payload. This metadata includes reference count, length etc. But the metadata for strings is not the same as for dynamic arrays. Simply put, a string is not a dynamic arrays. Your reinterpret cast is completely invalid. It was invalid in the old versions of Delphi, and it's just as invalid in the modern versions.

What's really going on under the hood is that the size of the metadata has changed with the introduction of Unicode support. The metadata for AnsiString has been expanded. It now contains, for instance, the code page of the string.

Now, when you call SetLength, a block of memory large enough for both metadata and payload is allocated. Suppose that memory is allocated at address P. The address that your variable (string or dynamic array) holds is set to P + metadata size. When you come to deallocate the object, the system moves to P - metadata size and calls FreeMem with that address. And here's the kicker. You use the size of a dynamic array metadata when you allocate, but the size of a string meta data when you deallocate. The result? BOOM! You just called FreeMem on an invalid address, one that has not been allocated to you.

The correct way to handle this is to give the Indy function what it wants. Namely a byte array. If you need to transfer to a string variable, copy the byte array's content into a new string. For instance using TEncoding.Default.GetString().

其他提示

Using your Fill procedure and your TIDBytes definition:

procedure Test;
var
  s: ansistring;
  b: TIDBytes;
begin
  fill(b);
  s := StringOf(TArray<byte>(b));
  ShowMessage(s);
end;

PS: @David answer is correct, but you could use directy the byte array and then use StringOf to get the string/ansistring

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top