Reputation: 27266
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
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
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