Jerry Dodge
Jerry Dodge

Reputation: 27266

Invalid Pointer Operation while freeing an Interfaced Object in a DLL

I'm still trying to come to grips with using Interfaces. I'm implementing them for the sole purpose of interacting with objects which are instantiated within a DLL. When I use it, everything works fine, all the methods work as expected, etc. The issue is when it comes to cleaning up the object behind that interface.

I have a simple interface like so

IMyInterface = interface
  ['{d52b14f3-156b-4df8-aa16-cb353193d27c}']
  procedure Foo;
end;

And an object for it

TMyObject = class(TInterfacedObject, IMyInterface)
private
  procedure Foo;
end;

Inside the DLL I have a global variable of this object as well as two exported functions to create and destroy this instance

var
  _MyObject: TMyObject;

function CreateMyObject: IMyInterface; stdcall;
begin
  _MyObject:= TMyObject.Create;
  Result:= IMyInterface(_MyObject);
end;

function DestroyMyObject: Integer; stdcall;
begin
  _MyObject.Free; //   <--   Invalid Pointer Operation
end;

The destructor of the object does virtually nothing, just inherited and I still have this issue. But I get Invalid Pointer Operation on _MyObject.Free.

I use LoadLibrary and GetProcAddress to access these exported methods.

Why am I getting this and how do I fix it?

Upvotes: 3

Views: 2560

Answers (2)

Alexey Petushkov
Alexey Petushkov

Reputation: 2158

You should not call .Free on TInterfacedObject-based class. It is freed automatically when last reference with interface is nil'ed.

Your code example should look like:

var
  _MyObject: IUnknown;

function CreateMyObject: IMyInterface; stdcall;
begin
  // unified interface
  _MyObject:= TMyObject.Create as IUnknown;
  // cast to IMyInterface
  Result:= _MyObject as IMyInterface;
end;

function DestroyMyObject: Integer; stdcall;
begin
  _MyObject := nil; 
end;

Upvotes: 2

Rob Kennedy
Rob Kennedy

Reputation: 163247

An invalid pointer operation means you freed something that wasn't allocated.

In this case, the object you're freeing has already been destroyed. Put a breakpoint in the destructor and see for yourself.

Interfaces have reference-counting code associated with them, which is why all the advice you've read about interfaces says not to mix them with object references, which have no such reference counting.

When you instantiate the object and assign it to your global variable, the object's reference count is zero, and interfaces aren't involved yet. When you assign it to the function result, the reference count becomes one. You can watch how that happens if you enable debug DCUs and step through that statement with the debugger. (The type cast isn't necessary, by the way; the compiler already knows that the object implements the target interface and will allow the simple assignment by itself.)

Elsewhere, on the consuming side of this DLL, the variable holding the last interface reference of the object gets cleared. The reference count becomes zero, and the object destroys itself.

Once the object is destroyed, your global variable is a dangling reference. It holds the address of an object that doesn't exist anymore. When you call Free on it, the destructor passes the address to the memory manager, but the memory manager knows that it doesn't have anything at that address (anymore), so it raises an exception.

To fix this, change the type of that global variable to be the interface type, and then remove the Free call; replace it with a statement assigning nil to the variable. With those changes, creating the object and storing an interface reference in the variable will set the object's reference count to one, and returning it to the caller will set it to two. When the consumer clears its reference, the count will drop to one, and the new nil assignment will set it to zero, making the object free itself at the proper time.

Once you start accessing an object through interface references, it's best not to use it through ordinary object references anymore. The risk is just too great that you'll accidentally use the object after its already been destroyed.

Upvotes: 6

Related Questions