Implementing Observer Pattern in Delphi with Interface - delphi

First of all, Hello for everybody and thanks for your help.
I'm trying to implement the Observer Pattern in Delphi using Interfaces, so an Object could be a Subject and an Observer at the same time.
I have a class that implements ISubject, with the following method:
procedure TSomeClass.Attach(const observer: IObserver);
var
I: Integer;
begin
if Fobservers = nil then
begin
Fobservers := TInterfaceList.Create;
end;
if Fobservers.IndexOf(Observer) < 0 then
Fobservers.Add(Observer);
end;
I followed Joanna Carter's example at http://blogs.teamb.com/joannacarter/2004/06/30/690.
In the application, I instantiate an object that implements the IObserver, and attach it to the TSomeClass object (which implements ISubject as well).
Then I call the Notify method from the TSomeClass object and it works correctly. My problem occurs when I try to FreeAndNil my Observer object, because I get an Invalid Pointer Operation, even though I'm using 'const' in the parameter and when I reach the FreeAndNil line in debug mode, the object is properly assigned, with all properties set and with a random property changed inside the notify.
I noted that I couldn't Free my object anymore when I call this line:
Fobservers.Add(Observer);
If I comment this line, then I can free my object. The code inside the application looks like this:
procedure TfrmAlisson.Button2Click(Sender: TObject);
var
locSomeClass: TSomeClass;
locObserver: TSomeObserverClass;
I: Integer;
begin
locObserver:= TSomeObserverClass.create(394693);
try
locSomeClass:= TSomeClass.create(263151);
try
locSomeClass.Attach(locObserver);
locSomeClass.NotifyObservers;
finally
FreeAndNil(locSomeClass);
end;
ShowMessage(IntToStr(locObserver.SomeProperty)); // This property is changed inside the notify
finally
locObserver.Free; // error
end;
end;
I would like to know why adding the IObserver to the TInterfaceList causes this (I'm using Delphi 2009).

Your TSomeObserverClass is most likely inheriting from TInterfacedObject.
When you pass it in Attach it gets passed as IObserver and this is where the reference counting kicks in. The RefCount goes to 1 when it gets added to Fobservers and when you destroy locSomeClass and with it the Fobservers list it gets removed again which causes the RefCount to drop to 0. Then the instance behind the IObserver interface reference is being destroyed.
To show the problem here is the minimal code to reproduce it:
var
obj: TInterfacedObject;
list: TInterfaceList;
begin
o := TInterfacedObject.Create;
try
list := TInterfaceList.Create;
list.Add(o);
list.Free;
finally
FreeAndNil(o);
end;
end;
If you execute this you see the EInvalidError in the FreeAndNil which is caused because the instance was already destroyed by the automatic reference counting implemented in TInterfacedObject.
As already commented you should not mix object and interface references or inherit from a class that does not implement automatic reference counting.

Related

DWScript: Getting from IScriptObj to IInfo or TProgramInfo

Given a IScriptObj reference how does one get to a corresponding IInfo or TProgramInfo?
I have a script object that wraps a Delphi object.
In order to manage the life time of the script object the Delphi object stores a reference to the script object. The Script object is declared with a TdwsUnit component. It's pretty standard and goes something like this:
Delphi
type
TDelphiObject = class
private
FScriptObject: IScriptObj;
public
procedure DoSomething;
property ScriptObject: IScriptObj read FScriptObject write FScriptObject;
end;
Script
type
TScriptObject = class
protected
procedure DoSomething; virtual;
public
constructor Create;
end;
The instantiation of the Delphi object and setup of the Delphi/script links happens in the Delphi implementation of the script object constructor. Also pretty standard:
Delphi
// Implements TScriptObject.Create
procedure TMyForm.dwsUnitClassesTScriptObjectConstructorsCreateEval(Info: TProgramInfo; var ExtObject: TObject);
var
DelphiObject: TDelphiObject;
DelphiObjectInfo: IInfo;
begin
// Create the Delphi-side object
DelphiObject := TDelphiObject.Create;
// Get the script object "self" value
DelphiObjectInfo := Info.Vars['self'];
// Store the ScriptObject reference
DelphiObject.ScriptObject := DelphiObjectInfo.ScriptObj;
// Return the instance reference to the script
ExtObject := DelphiObject;
end;
Ideally I would have saved the IInfo reference rather that the IScriptObj since IInfo does everything I need later on, but from experience it seems the IInfo object is only valid for the duration of the method call.
Anyway, the problem occurs later on when TDelphiObject.DoSomething is called on the Delphi side.
TDelphiObject.DoSomething is meant to call the corresponding virtual method on the script object:
Delphi
procedure TDelphiObject.DoSomething;
var
Info: IInfo;
DoSomethingInfo: IInfo;
begin
// I have a IScriptObj but I need a IInfo...
Info := { what happens here? };
// Call the virtual DoSomething method
DoSomethingInfo := Info.Method['DoSomething'];
DoSomethingInfo.Call([]);
end;
I have tried a lot of different techniques to get a usable IInfo or TProgramInfo from the stored IScriptObj but every thing has failed. So what is the correct way of doing this?
The problem turned out to be that I assumed I needed an IInfo interface to encapsulate the object instance but apparently DWScript doesn't work that way. What I need is to create a temporary reference/pointer to the instance and then create an IInfo on that instead.
Here's how that is done:
procedure TDelphiObject.DoSomething;
var
ProgramExecution: TdwsProgramExecution;
ProgramInfo: TProgramInfo;
Data: TData;
DataContext: IDataContext;
Info: IInfo;
DoSomethingInfo: IInfo;
begin
(*
** Create an IInfo that lets me access the object represented by the IScriptObj pointer.
*)
// FProgramExecution is the IdwsProgramExecution reference that is returned by
// TdwsMainProgram.CreateNewExecution and BeginNewExecution. I have stored this
// elsewhere.
ProgramExecution := TdwsProgramExecution(FProgramExecution);
ProgramInfo := ProgramExecution.AcquireProgramInfo(nil);
try
// Create a temporary reference object
SetLength(Data, 1);
Data[0] := FScriptObject;
ProgramInfo.Execution.DataContext_Create(Data, 0, DataContext);
// Wrap the reference
Info := TInfoClassObj.Create(ProgramInfo, FScriptObject.ClassSym, DataContext);
// Call the virtual DoSomething method
DoSomethingInfo := Info.Method['DoSomething'];
DoSomethingInfo.Call([]);
finally
ProgramExecution.ReleaseProgramInfo(ProgramInfo);
end;
end;
What this does is enable object oriented call backs from Delphi to the script. Without this it is only possible to call global script functions from Delphi.
FWIW, the following two lines from the above:
ProgramInfo.Execution.DataContext_Create(Data, 0, DataContext);
Info := TInfoClassObj.Create(ProgramInfo, FScriptObject.ClassSym, DataContext);
can be replaced with a call to CreateInfoOnSymbol (declared in dwsInfo):
CreateInfoOnSymbol(Info, ProgramInfo, FScriptObject.ClassSym, Data, 0);

Instantiated COM Component gets invalid after leaving method (but not its scope)

I am currently testing two external COM components. I have big issue with one of them, but I cannot really find reason behind such behavior. Let me provide some example.
const
CLASS_SomeClas: TGUID = '{SomeGUID}';
type
ISomeInterface = interface(IDispatch)
['{SomeGUID}']
function SomeMethod(const AInput: WideString): WideString; safecall;
end;
TWrappingClass = class(TObject)
strict private
FInstance: ISomeInterface;
procedure CreateInstance;
public
procedure DoYourActualJob;
end;
procedure TWrappingClass.CreateInstance;
begin
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
dbg(FInstance._AddRef); // Debugs 3
dbg(FInstance._AddRef); // Debugs 4
dbg(FInstance.Release); // Debugs 3
dbg(FInstance._AddRef); // Debugs 4
FInstance.SomeMethod(''); //Runs as expected
end;
procedure TWrappingClass.DoYourActualJob;
begin
CreateInstance;
dbg(FInstance._AddRef); //Debugs -1!
FInstance.SomeMethod(''); //AV
end;
As provided with example instance gets invalid after it leaves CreateInstance method. Component is designed to work with many sequential calls of SomeMethod and it does work when called inside single method.
Could someone give me clue what is actually happening there, why my instance gets invalid? Is it problem with my code, with Delphi or with component's code? When I change the implementation of TWrappingClass to another vendor (that is I change both ISomeInterface and CLASS_SomeClass) then everything works fine.
EDIT:
Behaviour does not change when I don't even call SomeMethod. That is after I leave CreateInstance, call to _AddRef returns -1. Component I am testing is here CadEditorX Probably I am not allowed to attach the OCX without violating its license.
You state clearly in the question that the erroneous behaviour only occurs with one specific COM object. Given this fact, and that Delphi's COM reference counting is known to work correctly, the only reasonable conclusion is that the fault lies in this specific COM object.
Your only recourse of action is to contact the vendor of this COM object and file a bug report with them.
One thing to look at, with a view to a possible work around, is how you are creating the object. You use CreateComObject. This receives a class ID and returns IUnknown. It calls CoCreateInstance passing the class ID, and requesting the IUnknown interface. You then need to query for your interface, ISomeInterface. So your code looks like this:
var
iunk: IUnknown;
intf: ISomeInteface;
....
CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER,
IUnknown, iunk);
iunk.QueryInterface(ISomeInterface, intf);
The fact that you have two interface variables, one IUnknown and one ISomeInterface explains why you see the reference count that you do. Now, you might think that you only have one interface variable, but that's not the case. There are two, only one of them is an implicit local. You can see this by looking at the compiled code and stepping through under the debugger.
This code:
procedure TWrappingClass.CreateInstance;
begin
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
end;
is compiled as if it were this (ignoring error checking):
procedure TWrappingClass.CreateInstance;
var
iunk: IUnknown;
begin
iunk := CreateComObject(CLASS_SomeClass);
try
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
finally
iunk := nil;
end;
end;
Perhaps the COM component cannot handle the call to Release made on its IUnknown interface.
So, you could try to work around this by using CoCreateInstance instead of CreateComObject. Pass ISomeInterface as the riid parameter.
OleCheck(CoCreateInstance(CLASS_SomeClass, nil, CLSCTX_INPROC_SERVER
or CLSCTX_LOCAL_SERVER, ISomeInterface, FInstance));

Constructing an Object from a Class Reference

I have a method which constructs an object, calls an Execute method, and frees the object. The type of object is determined by a TClass descendant passed into the method.
Note this is Delphi for Win32 I am talking about, not .NET.
Edit: I should point out that this is Delphi 2006, as it has been noted in answers below that in future versions the NewInstance call may not be required. In my case, however, it is required. As such, I would imagine the answer to my question (is it safe? and does CreateForm() have a potential leak) would need to be answered on the basis that this is Delphi 2006
Edit#2: seems that the solutions given for D2007 & D2009 do in fact work for D2006. I must have picked up the "NewInstance" habit from an earlier version of Delphi...
function TPageClassFactory.TryExecute(ScrnClass: TCustomPageClass): boolean;
//TCustomPageClass = class of TCustomPage
var
ScrnObj: TCustomPage; //TCustomPage defines an abstract Execute() method
begin
Result := FALSE; //default
ScrnObj := TCustomPage(ScrnClass.NewInstance); //instantiate
try
ScrnObj.Create(Self); //NB: Create() and Execute() are *virtual* methods
ScrnObj.Execute;
finally
FreeAndNil(ScrnObj);
end;
Result := TRUE;
end;
What I want to know is whether this is safe - what will happen here if Create() raises an exception?
Looking at a similar example, from Forms.pas.TApplication.CreateForm(), a different approach has been taken to exception handling (I've cut out the irrelevant bits below):
procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
var
Instance: TComponent;
begin
Instance := TComponent(InstanceClass.NewInstance);
TComponent(Reference) := Instance;
try
Instance.Create(Self);
except
TComponent(Reference) := nil;
raise;
end;
end;
In the Forms.pas method, does this mean that memory is leaked when an exception occurs in the Create() method? My understanding was that InstanceClass.NewInstance allocated memory, thus in this case the memory is not being deallocated/released/freed?
You should put the create out of the try finally block.
But a better solution is:
type
TMyClass = class ()
public
constructor Create(...); virtual;
function Execute: Boolean; virtual;
end;
TMyClassClass = class of TMyClass;
procedure CreateExecute(const AClass: TMyClassClass): Boolean;
var
theclass : TMyClass;
begin
theclass := AClass.Create;
try
Result := theclass.Execute;
finally
theclass.Free;
end;
end;
There have been a few questions raised in comments that I'd like to clarify.
First is the continued myth that the constructor needs to be virtual. It does not. Consider this example:
type
TBase = class
constructor Create(x: Integer);
end;
TDerived = class(TBase)
field: string;
end;
TMetaclass = class of TBase;
var
instance: TBase;
desiredClass: TMetaclass;
begin
desiredClass := TDerived;
instance := desiredClass.Create(23);
Assert(instance.ClassName = 'TDerived');
Assert(instance is TDerived);
Assert(instance.field = '');
end;
The created object will be a full-fledged instance of class TDerived. Enough memory will have been allocated to hold the string field, which didn't exist in the base class.
There are two conditions that must be true before you'll need a virtual constructor:
The constructor will be called virtually. That is, you'll have a variable of the base-class metaclass type, and it will hold a value of a derived class, and you will call a constructor on that variable. That's demonstrated in the code above. If all your constructor calls are directly on the class names themselves (i.e., TDerived.Create(23)), then there's nothing to be gained from virtual methods.
A subclass of the base class will need to override the constructor to provide class-specific initialization. If all descendants use the same construction, and only vary in other methods, ten there's no need to make the constructor virtual.
What's important to realize here is that those two rules are no different from the factors that determine when the make any other method virtual. Constructors aren't special in that regard.
The constructor knows which class to construct based not on the class where the constructor was defined, but on the class the constructor was called on, and that class is always passed as a hidden first parameter for every constructor call.
Second is the issue of whether NewInstance should be called in place of or in addition to the constructor. I think other comments have already established that it has nothing to do with compatibility with older Delphi versions. All versions have supported calling constructors on class references without the need for NewInstace. Rather, the confusion comes from looking at TApplication.CreateForm and treating it as an example of how things should be done. That's a mistake.
CreateForm calls NewInstance before calling the constructor because CreateForm's primary reason for existence is to ensure that the global form variable that the IDE declares is valid during the form's own event handlers, including OnCreate, which runs as part of the constructor. If the CreateForm method had done the usual construction pattern, then the global form variable would not yet have had a valid value. Here's what you might have expected to see:
TComponent(Reference) := InstanceClass.Create(Application);
Simple and obvious, but that won't work. Reference won't get assigned a value until after the constructor returns, which is long after the form has triggered some events. If you follow good programming practice and never refer to that variable from within the form class itself, then you'll never notice. But if you follow the documentation's instructions, which are written for an inexperienced audience, then you will refer to the global form variable from within the form's own methods, so the CreateForm method does what it can to make sure it's assigned in time.
To do that, it uses a two-step construction technique. First, allocate memory and assign the reference to the global variable:
Instance := TComponent(InstanceClass.NewInstance);
TComponent(Reference) := Instance;
Next, call the constructor on the instance, passing the TApplication object as the owner:
Instance.Create(Self);
It's my opinion that CreateForm should be called exactly once in any program. I'd prefer zero times, but it has the side effect of defining Application.MainForm, which is important for other aspects of a Delphi program.
Third is the notion that it's unusual for an object to call a constructor on itself.
In fact, this happens all the time. Every time you call an inherited constructor, you're calling a constructor on an object that already exists. The inherited constructor is not allocating a new object. Likewise, the VCL has some examples of non-inherited calls of constructors. TCustomForm.Create delegates much of its construction tasks to its CreateNew constructor.
Re your question about memory being leaked when Create() raises an exception: You should try it out for yourself. I just did on Delphi 2007, and with your code FastMM4 shows an error dialog about the attempt to call a virtual method on an already freed object, namely Destroy(). So the exception in Create will already lead to the destructor being called and the memory being freed, so your code is actually wrong. Stick to the idiom used in the answer by Gamecat, and everything should work.
Edit:
I just tried on Delphi 4, and the behaviour is the same. Test code:
type
TCrashComp = class(TComponent)
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
constructor TCrashComp.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
raise Exception.Create('foo');
end;
destructor TCrashComp.Destroy;
begin
Beep;
inherited Destroy;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
C: TComponent;
begin
C := TComponent(TCrashComp.NewInstance);
try
C.Create(nil);
C.Tag := 42;
finally
C.Free;
end;
end;
With FastMM4 the Free in the finally block gives the same error, because C has been freed already. On application shutdown the exception and the exception string are reported as memory leaks, though. This is however not a problem with the code, but with the runtime.
Edit:
Didn't fully remember how it was in old delphi versions but apparently this should work in all based on other replies.
Note, Create has been calling Destroy on fail for as long as I can remember. It shouldn't be after I think.
Code would be:
procedure TPageClassFactory.TryExecute(ScrnClass: TCustomPageClass);
var
ScrnObj: TCustomPage;
begin
ScrnObj := ScrnClass.Create(Self); // Exception here calls the destructor
try
ScrnObj.Execute; // Exception here means you need to free manually
finally
FreeAndNil(ScrnObj); // Be free!
end;
end;
I removed the result returned by the original function as it can never be false, only "unassigned" (exception) or true. You could after all get an exception before you assign result to false. ;)

Bypassing (disabling) Delphi's reference counting for interfaces

For one particular issue in the architecture of an application I'm working on, interfaces seem to be a nice solution. Specifically, some "business objects" depend on a bunch of settings that are pulled from the database in the actual app. Letting those business objects ask for an interface (through Inversion of Control), and letting a central TDatabaseSettings object implement those interfaces, allows for better isolation, and thus for much easier unit testing.
However, in Delphi, interfaces seem to come with an, in this case, unpleasant bonus: reference counting. This means that if I do something like this:
type
IMySettings = interface
function getMySetting: String;
end;
TDatabaseSettings = class(..., IMySettings)
//...
end;
TMyBusinessObject = class(TInterfacedObject, IMySettings)
property Settings: IMySettings read FSettings write FSettings;
end;
var
DatabaseSettings: TDatabaseSettings;
// global object (normally placed in a controller somewhere)
//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
// ... do something with O
O.Free;
On the last line (O.Free), my global DatabaseSettings object is now also freed, since the last interface reference to it (which was contained in O) is lost!
One solution would be to store the 'global' DatabaseSettings object with an interface; another solution would be to override the reference counting mechanism for the TDatabaseSettings class, so I can continue to manage the DatabaseSettings as a normal object (which is much more consistent with the rest of the app).
So, in summary, my question is: how do I disable the interface reference counting mechanism for a particular class?
I've been able to find some info that suggests overriding the IInterface methods _AddRef and _Release for the class (TDatabaseSettings in the example); has anyone ever done that?
Or would you say I shouldn't do this (confusing? just a bad idea?), and find a different solution to the architectural problem?
Thanks a lot!
Ok, you can bypass it, but the question is if you really want that.
If you want to use interfaces, you better use them completely. So as you have experienced it, you get problems if you mix class and interface variables.
var
// DatabaseSettings: TDatabaseSettings;
DatabaseSettings : IMySettings;
//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
// ... do something with O
O.Free;
You now have a second reference to the interface and losing the first will not free the object.
It as also possible to keep both the class and the object:
var
DatabaseSettings: TDatabaseSettings;
DatabaseSettingsInt : IMySettings;
Be sure to set the interface right after the object has been created.
If you really want to disable reference counting, you just have to create a new descendant of TObject that implements IInterface. I have tested the example below in D2009 and it works:
// Query Interface can stay the same because it does not depend on reference counting.
function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
constructor TMyInterfacedObject.Create;
begin
FRefCount := 1;
end;
procedure TMyInterfacedObject.FreeRef;
begin
if Self = nil then
Exit;
if InterlockedDecrement(FRefCount) = 0 then
Destroy;
end;
function TMyInterfacedObject._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;
function TMyInterfacedObject._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
FreeRef just lowers the refcount just like _Release. You can use it where you normally use Free.
Don't descend from TInterfacedObject, instead descend from TSingletonImplementation from standard System.Generics.Defaults unit.
TSingletonImplementation is a base for simple classes that need a basic IInterface implementation, with reference counting disabled.
TSingletonImplementation is a thread-safe base class for Delphi classes that support interfaces. Unlike TInterfacedObject, TSingletonImplementation does not implement reference counting.
_AddRef, _Release and _QueryInterface are, in fact, what you want to override. You should be very clear about what you're doing, however, as this can cause memory leaks or strange, hard-to-find bugs.
Don't descend from TInterfacedObject, instead descend from TObject, and implement your own versions of the first two of those methods that return 1.
To disable reference counting, AddRef and Release should do nothing but return -1
function TMyInterfacedObject._AddRef: Integer;
begin
Result := -1;
end;
function TMyInterfacedObject._Release: Integer;
begin
Result := -1;
end;
There is quite a lot of utility in interfaces without reference counting. If you use reference counting, then you cannot mix object and interface references as bad things will happen. By disabling ref counts, you can happily mix interface and object references without worrying about your objects suddenly getting auto destroyed.
Disabling reference counting for this kind of problem smells bad.
A much nicer and architectural solution would be to use some kind of "singleton" pattern.
The easiest way to implement this would look like:
interface
type
TDatabaseSettings = class(..., IMySettings)
end;
function DatabaseSettings: IMySettings;
implementation
var
GDatabaseSettings: IMySettings;
function DatabaseSettings: IMySettings;
begin
if GDatabaseSettings = nil then GDatabaseSettings := TDatabaseSettings.Create;
Result := GDatabaseSettings;
end;
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
O.Free;
By the way: when you use interfaces: always use interface variables! Do not mix both class en interface vars (use "var Settings: IMySettings" instead of "var Settings: TDatabaseSettings"). Otherwise reference counting will get in your way (auto destroy, invalid pointer operations, etc).
In the above solution, GDatabaseSettings is also of type "IMySettings", so it gets a proper reference count, and will last till your program terminates.
Or just use the code below:
var
I: IMyInterface;
begin
I := ...;
...
Do whatever you want in a scope;
Initialize(I); //- this will clear the interface variable without calling the _release.
end.

How should I free an array of objects in a Delphi 7 destructor?

Suppose my Delphi classes look like this:
interface
type
TMySubInfo = class
public
Name : string;
Date : TDateTime;
Age : Integer;
end;
TMyInfo = class
public
Name : string;
SubInfo : array of TMySubInfo;
destructor Destroy; override;
end;
implementation
destructor TMyInfo.Destroy;
begin
// hmmm..
end;
end.
To properly clean up, what should go in the destructor? Is it enough to do SetLength(SubInfo,0), or do I need to loop through and free each TMySubInfo? Do I need to do anything at all?
You need to loop through and free each created object.
You must know, that declaring a array of TMySubInfo doesn't actually create the objects. You have to create them later on.
I would use a TList instead for a more dynamic approach. You could even use a TObjectList that can free all its items when the list gets freed.
You should free each item, like this
destructor TMyInfo.Destroy;
var
I: Integer;
begin
for I:= Low(SubInfo) to High(SubInfo) do
SubInfo[I].Free;
SetLength(SubInfo, 0);
inherited;
end;
You free the objects the same way you allocated them. If you assigned an element's value by calling the constructor of a class, then free the object referenced by that element.
destructor TMyInfo.Destroy;
var
info: TMySubInfo;
begin
for info in SubInfo do
info.Free;
inherited;
end;
That uses syntax introduced in Delphi 2005. If you have an older version, use an explicit loop-control variable:
var
i: Integer;
begin
for i := 0 to High(SubInfo) do
SubInfo[i].Free;
You don't need to call SetLength at the end. A dynamic-array field like SubInfo gets released automatically when the object is destroyed. It works the same as interface, string, and Variant fields.
Agreed with all the above suggestions, but I want to add an (admittedly somewhat anal) recommendation that you always call the FreeAndNil() procedure in preference to the Free method.
Sooner or later you will accidentally access an object that you have already freed. If you have the habit of FreeAndNil-ing everything, then you get an immediate a/v on the line that contains the problem. If you merely Free'd the object, you will likely get a mysterious and apparently unconnected failure some time later...
This might seem obsessive in the context of a destructor, as here. Ok, it is a bit, but either one does it everywhere or not at all.
It is also anal, but Like to free in the reverse order to the creation, for example:
For I := List.Count-1 downto 0 do
List[I].Free;
I view creation and destruction as parethesis () although it makes litte actual execution diference.
Bri
If you created the objects via constructor calls, you need to make calls to Free to free them. If not, you don't.
For every new there should be a free.
Can you not use Finalize?

Resources