Delphi XE TBytes correct usage
What is the correct usage pattern for a TBytes variable? From my understanding, TBytes is not a class, but a "dynamic array of bytes". I'm not sure where memory gets allocated for it, when it is freed, and which is the best way to pass it from a producer to a consumer. I want my producer to create a TBytes instance, then pass it to a consumer. After this happens, the producer wants to reuse its TBytes member variable, content in the knowledge that the consumer will eventually return the memory to the system. If TBytes was an object, I wouldn't have any problem, but I'm not sure how the TBytes works in this scenario.
For example, in object A, I want to assemble some data into a TBytes array that is a member of object A. When that is complete, I then want to pass the TBytes array to another object B, which then becomes the owner of the data. Meanwhile, back in object A, I want to start assembling more data, reusing the TBytes member variable.
type
TClassA = class
private
FData: TBytes;
public
procedure AssembleInput(p: Pointer; n: Cardinal);
end;
TClassB = class
public
procedure ProcessData(d: TBytes);
end;
var
a: TClassA;
b: TClassB;
procedure TClassA.AssembleInput(p: Pointer; n: Cardinal);
begin
SetLength(FData, n);
Move(p^, FData, n开发者_Go百科); // Is this correct?
...
b.ProcessData(FData);
...
// Would it be legal to reuse FData now? Perhaps by copying new (different)
// data into it?
end;
procedure TClassB.ProcessData(d: TBytes);
begin
// B used the TBytes here. How does it free them?
SetLength(d, 0); // Does this free any dynamic memory behind the scenes?
end;
Thanks in advance!
Delphi dynamic arrays are managed types that have automatic lifetime management. They are reference counted and when the reference count goes to 0, there are disposed. You can think of them as being equivalent in that regard to strings, interfaces and variants.
You can explicitly release a reference to a dynamic array in one of three ways:
a := nil;
Finalize(a);
SetLength(a, 0);
However, it's very common simply to do nothing and let the reference be released when the variable leaves scope.
One thing to watch out for with dynamic arrays is when you have two references to the same dynamic array. In that situation, changes applied via one reference are visible from the other reference since there is only one object.
SetLength(a, 1);
a[0] := 42;
b := a;
b[0] := 666;//now a[0]=666
You ask if this is correct:
Move(p^, FData, n);
No it is not. What you have done here is to copy the contents of p
onto the reference FData
. If you want to copy with Move
then you can write:
Move(p^, Pointer(FData)^, n);
Or if you prefer to be a bit more verbose and avoid the cast you can write:
if n>0 then
Move(p^, FData[0], n);
I personally don't feel too bad about the cast since Move
has absolutely no type safety anyway.
Would it be legal to reuse FData now? Perhaps by copying new (different) data into it?
I don't feel I could answer this without more context. For example I don't know why FData
is a field since it is only used locally to that function. It would make more sense as a local variable. Presumably there is a reason it is declared as a field but it cannot easily be discerned from this code.
You about using the producer/consumer pattern. Normally this is done to decouple the production from the consumption. However, your example code does not do this, presumably because decoupled code would be too complex to include here.
For a true producer/consumer implementation you need to transfer ownership of the data from the producer to the consumer. From what we have described above, a very simple and effective way to do this is to use reference counting. When the data is transferred to the consumer, the producer should release its reference to it.
There are a couple of misuses in your code. The following would be more correct:
type
TClassA = class
private
FData: TBytes;
public
procedure AssembleInput(p: Pointer; n: NativeUInt);
end;
TClassB = class
public
procedure ProcessData(var d: TBytes);
end;
var
a: TClassA;
b: TClassB;
procedure TClassA.AssembleInput(p: Pointer; n: NativeUInt);
begin
SetLength(FData, n);
if n <> 0 then Move(p^, FData[0], n);
...
b.ProcessData(FData);
// FData is ready for reuse here...
end;
procedure TClassB.ProcessData(var d: TBytes);
begin
...
SetLength(d, 0);
end;
Move(p^, FData, n); This is OK
procedure TClassB.ProcessData(d: TBytes); // d is reference count of FData
begin
// d holds nothing but FData is stay as before with refcount = 1
// if you put "var" keyword in front of d, FData will be released
SetLength(d, 0);
end;
精彩评论