views:

270

answers:

7

I've a complex application to which I've just introduced some changes, adding a couple of new classes with interfaces and deleting some others. Functionally it all works but I get an access violation just after the Destroy procedure of a class:

"Access violation at address 0040B984 in module 'xxxx.exe'. Read of address 80808088".

I know this to be in the 'Finalize' code of the class and sure enough if I step into the dissassembly (Delphi 2010) I can see the point of the AV. I cannot see an easy way to find out which of my variables is triggering this though. Is there a procedure to be followed when going this deep that would get me a clue to the instance that is being referred-to?

Thanks Brian

+10  A: 

In most cases such errors can be caught by using FastMM and compiling application with conditional defines FullDebugMode and CatchUseOfFreedInterfaces. Just make sure that you put FastMM4 in the first place in the dpr's 'uses' list.

gabr
Good suggestion gabr, I am using FastMM but had not turned on 'CatchUseOfFreedInterfaces'. Now this gives me a tidy stack of the steps I was tracing before, but I'm no nearer getting a clue to 'who is holding' the illegal reference. I'm doing my own ref counting (-1) because I'm freeing my own classes (like TComponent does) so I need to have a clue to where the illegal interface pointer was 'got from'. Bri
Brian Frost
I had a similar problem a couple of weeks ago, caused by mixing reference and non-reference counted interfaces. I'm not sure why this caused a problem, but never the less, it did.
Vegar
+6  A: 

Steps to find the problem:

  1. Use FastMM in fulldebugmode as Gabr suggested (I think you already do, looking at the 808080 pattern).
  2. Set all interfaces you use in your class explicitly to nil in your Destroy procedure
  3. Put a breakpoint at the start of your Destroy procedure
  4. Now step through your Destroy procedure, when nilling the dangling interface you will get your Access Violation and you will know which interface it was.
  5. When you still have the AV after you nilled all your interfaces without a problem, do step 2 - 5 for the parent class.

I've had these problems too and the above method helped me find them. My problems were caused by TComponents that implemented interfaces. Say you have ComponentA and ComponentB, ComponentB implements an interface. You assign ComponentB (or its interface) to ComponentA and store the interface reference. Now ComponentB gets destroyed, but ComponentA doesn't know about that. When you destroy ComponentA it nils the interface, calls the _Release method and you get the AV.

The solution to that is to work with TComponent.FreeNotification. When you receive a free notification from ComponentB, you nil the interface in ComponentA. I don't know anything about your code, but if your problem is similar you can work with FreeNotifications too.

Edit: added step 5

The_Fox
@The Fox: I'll double check I've got nils everywhere!
Brian Frost
@Brian Frost: do you even know which class is producing the AV? Explicitly setting interfaces to nil is pretty straightforward, but can be cumbersome when having a lot of interfaces.
The_Fox
+7  A: 

This error looks like you are using FastMM for memory management. The error indicates that you are referring a pointer that has been cleared by FastMM with the DebugFillDWord value.

It means that you are using an interface that references to an object that has already been freed.
It also means you have not enabled CatchUseOfFreedInterfaces.

In order to change those, and to debug, you cannot do with the stock FastMM that comes with Delphi.
You will need to download FastMM (version 4.94).

After download:

Like gabr already mentions, inside FastMM4Options.inc, make sure you enable FullDebugMode and CatchUseOfFreedInterfaces (which disables CheckUseOfFreedBlocksOnShutdown, but you are not interested in the latter right now).
You might want to enable RawStackTraces as well; that depends if your current stack trace is good enough.

When you have done these settings, then run your app with FastMM through the debugger and put a breakpoint on this method inside the FastMM4 unit:

procedure TFreedObject.InterfaceError;

I have modified my FastMM4 unit a bit to get me more context info; I can share that with you (I have already mailed it to the FastMM4 team, but it has not been included in the official sources yet).

I wrote a pretty dense blog article on debugging using FastMM that might help you.
Drop a note here if that needs further explanation :-)

Good luck, and let us know if you need further directions.

--jeroen

Edit: 20100701 - emphasised the bits mentioned in Brian's comment.

Jeroen Pluimers
@jeroen: Thanks, I'd appreciate the context info mode that you mention. Already using FastMM 494 here but I'll go follow your blog steps. Bri
Brian Frost
@brian: drop me a note through my blog (or e-mail; almost anything at pluimers.com works, especially when you use my first name before the at sign)
Jeroen Pluimers
@All: The key point in this answer is: "When you have done these settings, then run your app with FastMM through the debugger and put a breakpoint on this method inside the FastMM4 unit: procedure TFreedObject.InterfaceError". Some way of determining more info about the resulting TFreedObject (i.e its class type) would be v.useful in FastMM.
Brian Frost
+2  A: 

One thing to look for in your code is this

FInterfacedObject.GetInterface 

in the same scope as

FInterfacedObject := TInterfacedObjectClass.Create.

where FInterfacedObject is a class variable.

You can calll GetInterface from a inner function if you want to, but if you call GetInterface in the same scope as you created FInterfacedObject, for whatever reason you'll drop the reference count to 0 and free the thing, but it won't be nil, so if you you do

if assigned(FInterfacedObject) then
    FInterfacedObject.Free;

you'll get an access violation.

Peter Turner
+2  A: 

Hello May be with a tool like EurekaLog ? http://delphi.about.com/od/productreviews/ss/eurekalog.htm

philnext
+3  A: 

A similar bug which has bitten me was a interface reference which has been set on an existing object, the interface reference counter will not decrease automatically when the owner object is freed. It can be solved with an if Assigned(FMyInterface) then FMyInterface := nil; in the owner object's destructor.

mjustin
Wise lesson learned: if you use interfaces, use them exclusively and let the refcounting mechanism manage everything for you.Try to avoid interfaces on TComponent descendants if possible, as the owner/components relation will compete with the refcounting mechanism causing one victim: the guy trying to debug the memory issues.
Jeroen Pluimers
+2  A: 

I do a similar thing, and the following code in the destructor of your object will help

Destructor TMyObjectThatIAmDoingManualRefCounting.Destroy;
begin
  if FMyRefCount<>0 then
    messageDlg('You dork, you called Free on me when someone still had references to me');

  inherited;
end;

Then you can at least find out where you are freeing the object innapropriately. Probably you are freeing it too early. Freeing the object too early will not actually cause a problem, it is when you are freeing the object that holds the interface reference to the already freed object that you will get the error.

The other thing you can do is put breakpoints in your addref and release methods and trace who is keeping the interface references and if those same object are freeing them afterwards.

Also a common problem is as follows, if you get an interface and free the object in the same method

var
  o:TSomeObject;
begin
  o:=TSomeObject.Create;
  (o as ISomeInterface).DoSomething;
  o.free
end;

This will cause an AV at the end of the method, because the compiler creates a fake interface variable that is freed at the end of the method.

you would need to do this

var
  o:TSomeObject;
  i:ISomeInterface;
begin
  o:=TSomeObject.Create;
  i:=(o as ISomeInterface); // or Supports or whatever
  i.DoSomething;
  i:=nil;
  o.free
end;
Dave Novo