This discussion went from Double Frees to Interfaces & Dependency Injection

Talking to Fable 5 [High Effort]


How do we get ‘double frees’ in Delphi code?

A “double free” in Delphi happens when the same object (or memory block) is destroyed/freed twice. The second free operates on memory that’s already been returned to the memory manager, which can corrupt heap structures, crash with EInvalidPointer/access violations, or cause subtle bugs that surface far from the actual mistake. Here are the common ways it happens in Delphi:

  1. Calling Free without nil-ing the reference
Obj.Free;
// ... later, another code path ...
Obj.Free;  // double free - Obj still points at the dead object

Free doesn’t set the reference to nil, so a second call frees the same memory again. This is why FreeAndNil(Obj) is popular — the second call then becomes a harmless no-op because Free checks for nil.

  1. Two references to the same object, both “owning” it
A := TMyThing.Create;
B := A;              // alias, not a copy
A.Free;
B.Free;              // double free

This is especially common when objects are passed around and it’s unclear who owns them.

  1. Owned components freed manually

VCL/FMX components created with an owner are destroyed by that owner:


Btn := TButton.Create(Form1);  // Form1 owns it
// ...
Btn.Free;                       // OK by itself, it de-registers from the owner

The classic bug is the reverse: keeping a reference to an owned component, letting the owner destroy it, then freeing (or using) the stale reference yourself. Similarly, TObjectList with OwnsObjects = True will free its items — if you also free them manually, that’s a double free.

  1. Freeing inside a destructor plus inherited cleanup

If a destructor frees a field and something else (an owned-object mechanism, a second destructor path, or an exception during Create triggering Destroy on a half-built object) frees it again. Remember: if a constructor raises an exception, Delphi automatically calls the destructor, so destructors must tolerate partially-initialized state — freeing a field that was already freed (or double-running cleanup logic) bites here.

  1. Interface reference counting mixed with manual Free

var
  Intf : IMyInterface;
  Obj  : TMyObject; // TInterfacedObject descendant
begin
  Obj  := TMyObject.Create;
  Intf := Obj;      // refcount = 1
  Obj.Free;         // manual free
end;                // Intf goes out of scope, _Release frees it again

Mixing object references and interface references to a ref-counted object is a well-known trap. Pick one lifetime model per object.

  1. Records/pointers with GetMem/FreeMem or Dispose
GetMem(P, Size);
FreeMem(P);
FreeMem(P);  // double free - P is a dangling pointer

Same pattern as objects: nothing nils the pointer for you.

  1. Event handlers / notifications firing during destruction

Code in OnDestroy, Notification, or callbacks that frees objects which are already in the middle of being destroyed elsewhere.

Defenses

  • Use FreeAndNil for fields and long-lived references so accidental second frees become no-ops (note it hides the logic error rather than fixing ownership, but it prevents corruption).
  • Establish clear, single ownership for every object; document who frees what.
  • Don’t mix manual Free with owning containers, component ownership, or interface ref-counting.
  • Run with FastMM4/FastMM5 in full debug mode — it detects double frees and use-after-free by filling freed memory with a pattern and keeping freed blocks quarantined, and reports the stack trace of both the free and the re-free. This is by far the fastest way to hunt these down.
  • ReportMemoryLeaksOnShutdown helps with the flip side (leaks) and often reveals confused ownership that leads to double frees.

If you’re debugging a specific crash, FastMM in debug mode is the tool to reach for first — want me to walk through setting that up?


I’m foggy on recalling the details, but people have pointed out to me ways in which Interface types can free memory out from under other parts of a program … can you comment?

Yes — this is one of Delphi’s sharpest edges. The core issue is that interface references are reference counted, and the compiler inserts _AddRef/_Release calls invisibly. When the count hits zero, the object destroys itself — regardless of whether other parts of your program still hold plain object references to it. Here are the main ways it bites:

  1. The fundamental trap: object reference + interface reference to the same object
var
  Obj  : TMyThing;          // TInterfacedObject descendant
  Intf : IMyInterface;
begin
  Obj  := TMyThing.Create;  // refcount = 0 (object refs don't count)
  Intf := Obj;              // refcount = 1
  Intf := nil;              // refcount = 0 → object frees ITSELF
  Obj.DoSomething;          // use-after-free; Obj is dangling
end;

Nothing warned you. Intf := nil (or Intf simply going out of scope) destroyed the object while Obj still pointed at it. This is exactly “freeing memory out from under” the rest of the program.

  1. Hidden temporary interface references

The compiler creates invisible temporaries when you use an object in an interface context, and it releases them at the end of the statement or (depending on version/construct) the end of the routine:

procedure Consume(const I: IMyInterface);

var
  Obj: TMyThing;
begin
  Obj := TMyThing.Create;      // refcount = 0
  Consume(Obj);                // implicit temp: refcount 0 → 1 → back to 0
  // object may already be destroyed here!
  Obj.DoSomething;             // dangling
end;

Passing an object where an interface is expected creates a temporary interface reference. When that temp is released, the count round-trips through zero and the object self-destructs. The same happens with as casts to an interface type used transiently, or passing Obj as IMyInterface as an argument.

The rule of thumb people derive from this: once an object’s lifetime is managed by interfaces, never touch it through a plain object reference again — and never let the ref-count touch zero “accidentally.” Assign to an interface variable immediately at creation:

var Intf: IMyInterface;
Intf := TMyThing.Create;   // refcount goes to 1 right away and stays owned
  1. Ref-count transiently hitting zero inside a method

A nastier variant: an object’s only interface reference gets released while one of its own methods is still executing — for example, a container removes the item mid-callback, or code inside the method reassigns the interface variable that was keeping it alive. The method continues running on a freed Self. This often “works” by luck until memory is reused.

A related classic is calling an interface method on a temporary and having the object die before you use the result you took from it (e.g., a PChar into the object’s internal string).

  1. Free on a ref-counted object — the double-free direction

The mirror image of #1: you free via the object reference, then the interface reference’s eventual _Release frees again, or worse, _Release decrements a ref-count field inside already-freed memory. Either way, heap corruption.

  1. Objects that implement interfaces but aren’t ref-counted

TComponent implements IInterface but its _Release does nothing (no self-destruction). So ref-counting silently doesn’t manage those lifetimes — which is safe from surprise-frees, but then the opposite bug appears: people assume the interface keeps the component alive, the component gets destroyed by its owner, and every interface reference to it becomes dangling without the ref-count mechanism noticing at all. Interfaces are not weak-safe pointers; a dangling interface to a destroyed component crashes just like a dangling object reference.

Any custom class can do the same by implementing _AddRef/_Release to return -1 and not free. Mixing ref-counted and non-ref-counted implementors of the same interface in one codebase is a rich source of confusion.

  1. Circular interface references

Two objects holding interfaces to each other never reach ref-count zero — a leak rather than a premature free — but the common fix (storing one side as a plain object pointer or unsafe “weak” reference) reintroduces the dangling-reference problem when one side dies first. Modern Delphi’s [weak] and [unsafe] attributes exist for this; [weak] references are auto-nil’ed on destruction, [unsafe] ones are not (they’re just uncounted, i.e., the footgun from #1 with a formal name).

  1. Exceptions and finalization order

Interface variables are finalized automatically when a routine exits (including via exception). If your cleanup code also frees the underlying object, or the finalization order releases the last reference earlier than you assumed, you get frees at moments you didn’t write any freeing code — which is precisely why these bugs feel like the runtime is pulling memory out from under you.

The practical doctrine that falls out of all this

  • Pick one lifetime model per object: either manual Create/Free with object references, or interface refcounting — never both.
  • If ref-counted: assign to an interface variable immediately on creation, and don’t keep or use plain object references to it afterward (or mark them [unsafe]/[weak] deliberately).
  • Beware implicit temporaries: passing objects to interface-typed parameters or using as ISomething inline can create-and-release a reference you never see.
  • For non-ref-counted implementors (components), remember the interface gives you access, not ownership — lifetime is still governed elsewhere.
  • FastMM in full debug mode catches the resulting use-after-free/double-free with stack traces of both the destruction and the illegal access, which is usually how people finally pin down which hidden _Release did the deed.

Is this connected to a specific crash you’re chasing, or more of a design-review question? If it’s a crash, the hidden-temporary case (#2) and the mixed Free+interface case (#4) are the two I’d bet on first.


Talking to Fable 5 [High Effort]


inline var declarations are an issue with interface references.

var MyIntf := TIntfImpl.Create; // will infer type as TIntfImpl not IMyIntf

what about
var MyIntf := TIntfImpl.Create as IMyIntf;

I wanted to write this anyway, when learning about interfaces, because we create them using classes … which I think can be a bit of a mind twist.

It seems like a possible idea, to be able to use Type Inference. What comments do you have about it?

Thought for 16s

Good instinct — you’ve spotted a real and well-known trap, and your workaround is legitimate. Let me walk through it.

The problem you identified is exactly right

var MyIntf := TIntfImpl.Create;   // inferred type: TIntfImpl

Type inference takes the static type of the expression, and a constructor returns the class type. So MyIntf is an object reference, refcount stays at 0, and you’re in the mixed-model danger zone from the previous discussion: the first time you pass it to an interface-typed parameter, a hidden temporary can send the refcount 0 → 1 → 0 and destroy it out from under you. The name MyIntf even lies to the reader about what it is. This is a known gotcha with inline vars (Delphi 10.3+).

Your as version works

var MyIntf := TIntfImpl.Create as IMyIntf;   // inferred type: IMyIntf

The as-cast’s static type is IMyIntf, so that’s what gets inferred, and the reference count goes straight to 1 in a single expression — there’s no window where the object exists only behind an uncounted object reference. From a lifetime perspective this is correct and safe.

Two caveats worth knowing:

  • as requires a GUID on the interface. An object-to-interface as-cast goes through QueryInterface at runtime, and that only works if the interface has a GUID. Without one, this won’t compile (or won’t resolve).
  • A failed cast leaks. The compiler doesn’t fully verify at the cast site that the class implements the interface; if QueryInterface fails at runtime you get EIntfCastError — and the freshly created object, with refcount 0 and no reference held anywhere, leaks. In practice you’d hit this immediately in testing, so it’s a small risk, but it’s there.

The alternative most Delphi folk prefer: annotate instead of cast

Inline vars let you state the type explicitly and still initialize inline:

var MyIntf: IMyIntf := TIntfImpl.Create;

This uses the ordinary implicit object-to-interface assignment: no GUID required, no runtime QueryInterface, can’t fail-and-leak, and it documents intent right at the declaration. It costs you type inference, but here inference wasn’t buying you anything — you want to pin the type, because the whole point is that the variable’s type (interface vs. class) determines the lifetime model. Making it explicit is a feature, not a concession.

The idiom that resolves your “mind twist”

Your discomfort — “we create interfaces using classes” — is shared, and the classic Delphi answer is the factory function:

// in the unit's interface section:
function NewThing(const AName: string): IMyIntf;

// implementation:
type
  TIntfImpl = class(TInterfacedObject, IMyIntf)
...
end;

function NewThing(const AName: string): IMyIntf;
begin
  Result := TIntfImpl.Create(AName);
end;

Now the class can live entirely in the implementation section — consumers literally cannot see it, cannot create it directly, and cannot accidentally hold an object reference to it. And notice what this does for your original wish:

var MyIntf := NewThing('demo');   // inferred type: IMyIntf. Inference is now safe!

Type inference becomes trustworthy again, because the expression’s type is already the interface. This is the cleanest resolution of the tension: rather than fighting inference at each call site with casts or annotations, make the creation expression itself interface-typed once, at the factory.

Summary judgment

  • Bare var x := TIntfImpl.Create — avoid; wrong type, wrong lifetime model, misleading name.
  • … Create as IMyIntf — works, safe refcount-wise; needs a GUID, tiny leak-on-failure edge, slight runtime cost.
  • var x: IMyIntf := TIntfImpl.Create — the conventional, robust choice when you construct classes directly.
  • Factory returning the interface — best of all worlds; hides the class, makes inference safe, and enforces “one lifetime model per object” structurally rather than by discipline.

The deeper principle your question circles: with interfaces, the declared type of the reference is part of the program’s correctness, not just its documentation. That’s why leaning on inference at exactly the class/interface boundary is the one place it’s riskiest — and why pushing the boundary into a factory, where it’s decided once, is so idiomatic in Delphi.


Ignoring other parameters that could be required .. could we, in principle, make a generic Interface Constructor factory that takes the object type, the interface type, and bakes them in to an anonymous function?

Thought for 15s

Yes — in principle and in practice, and it’s a fun exercise because it runs you straight into three or four of Delphi’s generics quirks. Let me sketch it and then flag the traps.

The basic shape

uses
System.SysUtils, System.TypInfo;


type
  TInterfaceFactory = class
  public
    class function CreateFactory<T: class, constructor; I: IInterface>: TFunc<I>;
  end;

  class function TInterfaceFactory.CreateFactory<T, I>: TFunc<I>;
  begin
    Result :=
      function: I
      var Obj: T;
      begin
        Obj := T.Create;
        if not Supports(Obj, GetTypeData(TypeInfo(I))^.Guid, Result) then 
          begin
            Obj.Free;  // refcount is still 0; safe and prevents the leak
            raise EIntfCastError.CreateFmt( '%s does not implement %s',
                                            [T.ClassName, GetTypeName(TypeInfo(I))] );
          end;
      end;
  end;

Usage:

var NewThing := TInterfaceFactory.CreateFactory<TIntfImpl, IMyIntf>;
// NewThing is TFunc — inference on it is safe
var MyIntf := NewThing();   // inferred as IMyIntf

So yes: you bake both types into a closure once, and every call site gets a correctly interface-typed expression, which restores trustworthy type inference — the factory-function idiom from before, generated generically. Note it also fixes the leak-on-failure edge from the as cast: because we hold Obj as a plain reference during the query, we can Free it explicitly if the cast fails.

Now the traps this design walks into:

  1. The constructor constraint is weaker than it looks. T: class, constructor promises a parameterless constructor, but in generic code T.Create is statically bound — for an ordinary (non-virtual) constructor, it effectively calls TObject.Create on an instance of T rather than your class’s constructor. Your fields don’t get initialized by your Create’s code. The classic fix is to demand a virtual constructor via a base-class constraint:
type
  TFactoryBase = class(TInterfacedObject)
  public
    constructor Create; virtual;
  end;

// then: <T: TFactoryBase, constructor; I: IInterface>

Now T.Create dispatches virtually and your derived constructor actually runs. If your implementation classes do all their setup in AfterConstruction or have no constructor logic, the plain constraint works — but that’s fragile, so the virtual-constructor base is the standard move.

  1. No compile-time proof that T implements I. Generics can’t express “T implements I” as a constraint, so the class/interface relationship is checked at runtime via QueryInterface — that’s what the Supports call and the raise are doing. You’ve traded a compile error for a startup-time error. Acceptable if factories are created early (composition root), where a mistake explodes on first run; less nice if factories are built lazily deep in the app.

  2. I needs a GUID, same as the as cast — TypeInfo(I) gives us the GUID for Supports, and without one the lookup can’t work.

  3. Parameters — you said to ignore them, but worth one sentence: the clean generalization is to accept a TFunc<T> “creator” delegate instead of using the constructor constraint, i.e. CreateFactory<T, I>(const ACreator: TFunc): TFunc<I>. Then the caller writes function: T begin Result := TIntfImpl.Create(42) end and you sidestep both the constructor-binding trap and the parameterless limitation in one move. The generic wrapper’s only remaining job is the object→interface conversion and failure handling — which is exactly the part worth centralizing.

  4. One lifetime subtlety in the closure. Notice the anonymous function creates a fresh instance per call — good. The tempting “optimization” of creating the object outside the closure and returning the same interface every call turns your factory into a singleton-with-shared-state, and captures the object reference in the closure for the program’s lifetime. Sometimes that’s what you want (it’s how lazy singletons get built), but it should be a deliberate second method (CreateSingleton<T, I>), not an accident.

The honest assessment

What you’ve designed is, in miniature, the registration half of a dependency-injection container — Spring4D’s container is essentially this idea industrialized: RegisterType.Implements, plus lifetime management (per-call vs. singleton), constructor-parameter injection, and the runtime implements-check done eagerly at registration. So the concept is not just workable in principle; it’s load-bearing in real Delphi ecosystems.

For learning purposes, I’d actually encourage building your version — it forces you to touch TypeInfo, GUIDs, Supports, closure capture, and the constructor-constraint quirk, which between them cover most of the machinery that makes Delphi interfaces tick. And the end result genuinely solves your original itch: every creation expression in the program becomes interface-typed at the source, so var x := Factory() infers the right thing everywhere, and the class types can retreat into implementation sections where nobody can mix lifetime models by accident.