I have a Delphi Form that provides the functionality behind an interface object that other parts of the code get references too via a property belonging to the Form. I can't delegate the interface functionality to a child object because too much of that functionality is serviced by controls/components on the form. I can't use TAggregatedObject or TContainedObject to link the lifetime of the interfaced objects being passed around to the Form because the TForm class does not inherit from TinterfacedObject and Delphi does not support multiple inheritance so I can't mix in TInterfacedObject into the inheritance chain. This situation can lead to access violations if a Form gets destroyed while some other code holds one of the interface references passed out by the Form. Can anyone think of a good solution to this problem?
2 Answers
You can delegate the interface to a child object, just have that object contain an internal pointer to the Form so it can access the Form's controls when needed, no different then you are already doing right now.
You can use TAggregateObject
or TContainedObject
for your needs. They do not require the Form to derive from TInterfacedObject
. All they do require is an IInterface
interface pointer, and TComponent
derives from IInterface
(and overrides the _AddRef()
and _Release()
to disable reference counting), so you can pass the Form itself (being a TComponent
descendant) as the required IInterface
pointer.
That leaves the only issue remaining - the Form closing while active interface references are being held by other code. The simpliest solution is to either 1) rewrite that code to not hold on to those references while the Form is closing, or 2) don't allow the Form to close until those references have been released.

- 555,201
- 31
- 458
- 770
-
Pardon the late question, but can you point me to a good reference that explains when to use TContainedObject instead of TAggregatedObject? I've stared at the Delphi Help for a while and can't really flesh out the use case differences. – Robert Oschler Oct 30 '11 at 17:27
Note: This will only work, if your consumer is also derived from TComponent.
To avoid the dead references you can query the IInterfaceComponentReference
(available on every TComponent) from your form, call GetComponent
on that interface and attach yourself to the FreeNotification
of the returned Component/Form.
What happens now is: When the Form gets destroyed it will notify all "listners" that its going to destroy itself by calling the Notification
method on the consumer with itself (form) as AComponent
and opRemove
as operation. Thus allowing you to nil your interface reference.
But be aware that the object references and interface references must not be equal.
Also make sure to call RemoveFreeNotification
when you don't need the Notification any more to avoid unnecessary calls.
TSomeConsumer = class(TComponent)
private
FInterfaceToAService: ISomeInterface;
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
procedure SetService(const Value: ISomeInterface);
end;
procedure TSomeConsumer.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited;
if (Operation = opRemove) and (AComponent = TObject(FInterfaceToAService)) then
SetService(nil); // Takes care of niling the interface as well.
end;
procedure TSomeConsumer.SetService(const Value: ISomeInterface);
var
comRef: IInterfaceComponentReference;
begin
if Supports(FInterfaceToAService, IInterfaceComponentReference, comRef) then
comRef.GetComponent.RemoveFreeNotification(self);
FInterfaceToAService := Value;
if Supports(FInterfaceToAService, IInterfaceComponentReference, comRef) then
comRef.GetComponent.FreeNotification(self);
end;

- 928
- 8
- 22
-
Nice! I didn't know about that. Could you expand a little on your statement - "But be aware that the object references and interface references must not be equal."? – Robert Oschler Oct 11 '11 at 13:21
-
1Well, several reasons: a) you can make a custom implementation of `QueryInterface` which may return a new interfaced object on every call. b ) Secondly you can delegate the Interface to a property (Win32), which could again create a new Object every time. c) The implementation of `TObject.GetInterface` adds an interfaceOffset to the address. (But these internals are unfortunately beyond may knowledge) The newly introduced (Delphi 2010) Interface to object casting is basically done by querying a marker interface which returns the entry address of the object. – Tobias R Oct 11 '11 at 15:04
-
What will happen if the object and interface references are equal and what is the most common mistake by a programmer that ends up creating that unwanted situation? – Robert Oschler Oct 11 '11 at 17:28
-
1I can't think of a problem, that could arise beyond an unnecessary notification call. This binding is what happens when you connect/insert components and controls in delphi. Only that the lifetime management is done implicitly by the parent/owner form omitting the explicit `FreeNotification` registration. Using an interface reference instead of a `TComponent` is just a bit more overhead. – Tobias R Oct 11 '11 at 18:36