Ever see an access violation in _IntfClear (in System.pas)? Typically these come up when you’re closing a form or shutting down an app. Unless you build with Debug DCUs, you may not even recognize that the error is in _IntfClear. Note that this applies only to Delphi for Win32 — you’ll never see the problem in Delphi for .NET.
The problem is caused by using an interface on a class which does not implement reference counting. Notably, this is true of any TComponent subclass, TXMLDocument when it is created in certain ways, and any class where you’ve overridden _AddRef and _Release to return -1 instead of incrementing/decrementing a value.
With such a class the lifetime of instances is not managed by reference counting. In the case of a TComponent, for example, it’s usually managed by Ownership. However, due to the nature of interface references, _AddRef and _Release are still going to be called when the reference goes out of scope. If the class has been freed prior to that time, then you have an AV in _IntfClear.
This is one of the reasons why mixing object references and interface references can be dangerous.
The solution to the problem is to find all interface references in your code and make sure that you set them to nil before the object which implements the interface is destroyed. This can be tricky to do because there is not a good way (that I know of) to determine which reference is causing the AV.
But I bet it will solve your problem.
{ 7 } Comments
Please, take a look at QC 9500
Pablo, I closed that report as it isn’t a bug and there’s already an earlier feature request for the issue. See the report for more detailed comments.
I prefer to respond here because I don’t want to open QC 9500 again.
I think the problem is the difference between "object/interface lifetime" and "object/interface reference".
I’m not mixing "object/interface lifetime" but I do mixing "object/interface reference". I’m using descendants of TComponent implementing interfaces. I want to create and destroy an object and I want to reference an interface.
I understand it is an error to mix "object/interface lifetime" but
why is not possible to mix "object/interface reference"?.
If I don’t want to use "interface lifetime" I should have a way to tell the compiler not to add calls to _AddRef and _Release.
The compiler always implement reference count when using interfaces (not to destroy the object but to call _AddRef and _Release).
Your solution to the problem (last paragraph) is to tell the compiler when to call _Release to prevent a call to _Release after the object was destroyed.
I you prefer I can add this post to QC 9500.
Thank you for your response.
Pablo,
It is possible — but tricky — to mix object and interface references in Win32 because interfaces in Win32 are designed to be reference counted. You can avoid the actual reference counting by returning -1 for _AddRef and _Release, but you can’t change the design.
The feature request you just made in your comment (never call _AddRef or _Release at all) is already in QC in a much older report. I referenced the exact number in my comment on your report. So there is no need to add this feature request to your report because it is already in QC. Again, see my comment in QC for the number of that request in case you’d like to vote on it.
Finally, note that this is never a problem in Delphi for .NET since garbage collection means that reference counting is never necessary in managed code.
So all Interface variables should be nil after destroying the object behind it.
For global Interface variables I put some code in the finalization part of the unit and it works:
fw: IBase;
finalization
if Assigned(fw) then begin
Pointer(fw) := nil;
end;
end.
Craig,
Thank you very much for your response.
I had read QC 9157 before creating QC 9500 but I decided to create QC 9500 because the workaround of QC 9157 didn’t work for me. I also had already voted for QC 9157 and you are right, the feature request of both cases is the same.
In my code sometime is impossible to assign nil to an interface reference variable because there is not such a variable. For example:
function GetSomething: IMyInterface;
procedure DoSomething;
begin
GetSomething.DoSomething;
// For some reason destroy the object (IMyInterface implementor)
end;
In this case the compiler calls _Release at the end of DoSomething procedure.
Anyway, I have found a workaround that works for me so this is not a problem for me anymore.
PD: In Delphi .NET I have another problem. See QC 6790
Andreas,
I’m doing this:
type
TInterfacedComponent = class(TComponent, IInterface)
private
FRefCount: integer;
FDestroyObject: boolean;
protected
{ IInterface }
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
procedure BeforeDestruction; override;
destructor Destroy; override;
procedure FreeInstance; override;
end;
implementation
{ TInterfacedComponent }
procedure TInterfacedComponent.BeforeDestruction;
begin
// The object is destroyed only if there are no references to it
if FRefCount = 0 then
inherited;
end;
destructor TInterfacedComponent.Destroy;
begin
// The object is destroyed only if there are no references to it
if FRefCount = 0 then
inherited
else
// Flag to call free on the last call to _Release
FDestroyObject := True;
end;
procedure TInterfacedComponent.FreeInstance;
begin
// The object is destroyed only if there are no references to it
if FRefCount = 0 then
inherited;
end;
function TInterfacedComponent._AddRef: Integer;
begin
// No reference count is taking place
Result := -1;
Inc(FRefCount);
end;
function TInterfacedComponent._Release: Integer;
begin
// No reference count is taking place
Result := -1;
Dec(FRefCount);
if (FRefCount = 0) and FDestroyObject then
Free;
end;
end.
Problems I have found with this:
- Destroy could be called more than once so I must use FreeAndNil to destroy internal objects
I also have similar classes for TDataModule, TForm and TFrame. So all my classes implementing interfaces descends from these classes.
Post a Comment