user1009073
user1009073

Reputation: 3238

Delphi - TObjectDictionary giving Invalid Pointer on Free

Delphi XE6 - I am using TObjectDictionary, with a custom class. I create, add, and later Free. It I do JUST that, everything is fine. If I do a "TryGetValue", I get an "Invalid Pointer Operation" on my free. The TryGetValue works fine, gives the proper result, but causes an issue later...

Here is my code.

 type
  TWordRec = class
  public
    RemoveAlways: Boolean; // Is this CORP LLC, etc?
    RemoveRestricted: Boolean;
    Replace: Boolean;
    ReplaceWith: string;
    Constructor Create(B1, B2, B3: Boolean; S1: String); overload;
  end;
...
constructor TWordRec.Create(B1, B2, B3: Boolean; S1: String);
begin
  self.RemoveAlways := B1; // Is this CORP LLC, etc?
  self.RemoveRestricted := B2;
  self.Replace := B3;
  self.ReplaceWith := S1;
end;

I add my test code on a button...

procedure TForm1.Button1Click(Sender: TObject);
var
CurrentDictEntry : TWordRec;
FoundIt : Boolean;

begin

ReportMemoryLeaksOnShutdown := True;

WordDict := TObjectDictionary<String, TWordRec>.Create([doOwnsValues]);
WordDict.Add('CO', TWordRec.Create(True, False, False, ''));
WordDict.Add('CORP', TWordRec.Create(True, False, False, ''));
WordDict.Add('CORPORATION', TWordRec.Create(True, False, False, ''));

CurrentDictEntry := TWordRec.Create(False, False, False, '');

// If I remove next two code lines, everything is fine
// With these 2 lines, I get error ON WordDict.Free;
FoundIt := WordDict.TryGetValue('CORP', CurrentDictEntry);
if FoundIt = True then ShowMessage('Found');

CurrentDictEntry.Free;
WordDict.Free;
end;

Why can't I free my WordDict? If I just free WordDict (i.e. comment out CurrentDictEntry.free), the WordDict.Free works without an Invalid Pointer operation, but then I get a memory leak on exiting my app.

Upvotes: 1

Views: 727

Answers (1)

David Heffernan
David Heffernan

Reputation: 612954

The main problem is here:

CurrentDictEntry.Free;

At this point, CurrentDictEntry is a value that is contained in the container. And you cannot free the values in this container because the container owns them. If you try to free values, then the container will later try to free them again, the classic double free problem.

Once you've told the container to own the values, you must not free them.

The other problem is that you leak a TWordRec.

CurrentDictEntry := TWordRec.Create(False, False, False, '');
FoundIt := WordDict.TryGetValue('CORP', CurrentDictEntry);

The first line creates a new object and holds a reference to it in CurrentDictEntry. The second line then overwrites CurrentDictEntry and now you have no references at all to the object that you just created, and it is thus leaked.

Your code should look like this:

ReportMemoryLeaksOnShutdown := True;

WordDict := TObjectDictionary<String, TWordRec>.Create([doOwnsValues]);
try
  WordDict.Add('CO', TWordRec.Create(True, False, False, ''));
  WordDict.Add('CORP', TWordRec.Create(True, False, False, ''));
  WordDict.Add('CORPORATION', TWordRec.Create(True, False, False, ''));

  FoundIt := WordDict.TryGetValue('CORP', CurrentDictEntry);
  if FoundIt then 
    ShowMessage('Found');
finally
  WordDict.Free;
end;

Upvotes: 3

Related Questions