2
votes
unit example;

interface

type
    ILettersSettings = interface
        function Letters: String;
    end;

    INumbersSettings = interface
        function Numbers: String;
    end;

    TSettings = class(TInterfacedObject, ILettersSettings, INumbersSettings)
    private
        fLoadedLetters: String;
        fLoadedNumbers: String;
    public
        procedure LoadFromFile;
    private {ILettersSettings}
        function Letters: String;
    private {INumbersSettings}
        function Numbers: String;
    end;

    TNumbers = class
    private
        fNumbers: String;
    public
        constructor Create(settings: INumbersSettings);
    end;

    TLetters = class
    private
        fLetters: String;
    public
        constructor Create(settings: ILettersSettings);
    end;


implementation

{ TSettings }

procedure TSettings.LoadFromFile;
begin
    fLoadedLetters := 'abc';
    fLoadedNumbers := '123';
end;

function TSettings.Letters: String;
begin
    result := fLoadedLetters;
end;

function TSettings.Numbers: String;
begin
    result := fLoadedNumbers;
end;

{ TNumbers }

constructor TNumbers.Create(settings: INumbersSettings);
begin
    fNumbers := settings.Numbers;
end;

{ TLetters }

constructor TLetters.Create(settings: ILettersSettings);
begin
    fLetters := settings.Letters;
end;

var
    settings: TSettings;
    letters: TLetters;
    numbers: TNumbers;
begin
    settings := TSettings.Create;
    settings.LoadFromFile;

    letters := TLetters.Create(settings);
    numbers := TNumbers.Create(settings);
end.

I have object with settings for whole project.

   settings := TSettings.Create;
   settings.LoadFromFile;

I use this object to create two objects: numbers and letters, by inject it by constructor.

letters := TLetters.Create(settings);
numbers := TNumbers.Create(settings);

But I dont assign it to any variable inside constructor, just use it.

{ TNumbers }
constructor TNumbers.Create(settings: INumbersSettings);
begin
    fNumbers := settings.Numbers;
end;

{ TLetters }
constructor TLetters.Create(settings: ILettersSettings);
begin
    fLetters := settings.Letters;
end;

So at the begin of constructor there is made reference count = 1, and on the end of constructor reference count is decreace to 0, and object is destroyed. So in line:

numbers := TNumbers.Create(settings);

There is inject nil and Runtime Error is raised.

How fix it?

2
What you are doing here seems nonsensical. What are you trying to achieve? - Dsm
I have file with settings for whole application and I load this file to one object. I share this object by interfaces one interface to one class which needs settings. - aQuu

2 Answers

4
votes

The problem is that you are mixing two different approaches to lifetime management. You have a mix of reference counted lifetime management, and programmer controlled lifetime management.

Your variable settings is declared to be of type TSettings. Although you did not show that declaration, we know this to be so because you are able to call LoadFromFile. That's only possible if settings is declared to be of type TSettings.

Because settings is a class, this means that your code is responsible for its lifetime. As such, the compiler does not emit reference counting code when you assign to settings.

However, when you call TLetters.Create and TNumbers.Create, you pass interface references, to ILetters and INumbers respectively. For this code, the compiler does emit reference counting code. The reference count goes up to 1 when you obtain an interface reference, and then down to zero when that reference leaves scope. At which point the implementing object is destroyed.

The fundamental problem in all of this is that you have broken the lifetime management rules. You must not mix the two different approaches as you have done.

The usual policy that people adopt is to either use programmer controlled management always, or reference counted management always. The choice is yours.

If you wish to use reference counted management exclusively then you would need to ensure that all functionality of your settings class was available via interfaces. That would mean making sure that LoadFromFile could be called via an interface. Or perhaps arranging for it to be called by the constructor.

Alternatively you could switch to programmer controlled management. In that case you must not derive from TInterfacedObject. You might instead derive from a class like this:

type
  TInterfacedObjectWithoutReferenceCounting = class(TObject, IInterface)
  protected
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

function TInterfacedObjectWithoutReferenceCounting.QueryInterface(const IID: TGUID; 
  out Obj): HResult;
begin
  if GetInterface(IID, Obj) then begin
    Result := S_OK;
  end else begin
    Result := E_NOINTERFACE;
  end;
end;

function TInterfacedObjectWithoutReferenceCounting._AddRef: Integer;
begin
  Result := -1;
end;

function TInterfacedObjectWithoutReferenceCounting._Release: Integer;
begin
  Result := -1;
end;

But that comes with its own risks. You must make sure that you do not hold any references to the object after the object has been destroyed.

0
votes

There are many ways to fix that... The simplest would probably be to have TSettings inherit from TComponent instead of TInterfacedObject.

TComponent implements IInterface but doesn't not implement the reference counting by default, so when the refcount is decremented, the object won't be destroyed. That also means you have to destroy it yourself.

TSettings = class(TComponent, ILettersSettings, INumbersSettings)

[...]
settings := TSettings.Create;
try
  settings.LoadFromFile;

  letters := TLetters.Create(settings);
  numbers := TNumbers.Create(settings);
finally
  Settings.Free;
end;