Question

If I understand correctly this is fine:

type
  IMyInterface = interface['{60E314E4-9FA9-4E29-A09A-01B91F2F27C7}']  
    procedure MyMethod;
  end;

type
  TMyIClass = class(TInterfacedObject, IMyInterface)
  public
    procedure MyMethod;  // Forget the implementations in this example
  end;

var
   lMyIClass: IMyInterface;
   lSupports: Boolean;
begin
   lMyIClass := TMyIClass.Create;
   lSupports := Supports(lMyIClass,IMyInterface);
   Memo1.Lines.Add('lMyIClass supports IMyInterface: ' + BoolToStr(lSupports,true));
   if lSupports then DoSomethingWith(lMyIClass);

Now I have a class implementing multiple interfaces:

type
   IFirstInterface = interface['{4646BD44-FDBC-4E26-A497-D9E48F7EFCF9}']
     procedure SomeMethod1;
   end;

   ISecondInterface = interface['{B4473616-CF1F-4E88-9EAE-1AAF1B01A331}']
     procedure SomeMethod2;
   end;

   TMyClass = class(TInterfacedObject, IFirstInterface, ISecondInterface)
     procedure SomeMethod1;
     procedure SomeMethod2;
   end;

I can call another overloaded Support() returning the interface and do something with it):

var
   MyClass1,MyClass2 : TMyClass;
   i1: IFirstInterface;
   i2: ISecondInterface;
   bSupports: Boolean;
begin
    Memo1.Clear;
    MyClass1 := TMyClass.Create;

    bSupports := Supports(MyClass1,IFirstInterface,i1);  
    if bSupports then 
    begin
        Memo1.Lines.Add('MyClass1 supports IFirstInterface');
        DoSomethingWith(i1);
    end
    else
        Memo1.Lines.Add('MyClass1 does not support IFirstInterface');

    bSupports := Supports(MyClass1,ISecondInterface,i2);    
    if bSupports then 
    begin
        Memo1.Lines.Add('MyClass1 supports ISecondInterface');
        DoSomethingElseWith(i2);
    end
    else
        Memo1.Lines.Add('MyClass1 does not support ISecondInterface');

    MyClass1 := nil;
    i1 := nil;
    i2 := nil;
    MyClass2 := TMyClass.Create;

    bSupports := Supports(MyClass2,IFirstInterface,i1);

    if bSupports then 
    begin
        Memo1.Lines.Add('MyClass2 supports IFirstInterface');
        DoSomethingWith(i1);
    end
    else
        Memo1.Lines.Add('MyClass2 does not support IFirstInterface');

    bSupports := Supports(MyClass2,ISecondInterface,i2);
    if bSupports then 
    begin
        Memo1.Lines.Add('MyClass2 supports ISecondInterface');
        DoSomethingElseWith(i2);
    end
    else
        Memo1.Lines.Add('MyClass2 does not support ISecondInterface');

I have three questions about this:

  1. The MyClass1, MyClass2 are now object types, not interface types as in the simple example. Is this OK?

  2. Should I Free() or 'nil' MyClass1 or maybe even leave it alone?

  3. After having handled 2., are the two ix:= nil statements still required with regard to the reference counts?

Was it helpful?

Solution

A common piece of advice is to never mix object references with interface references. What that means is that if you need to instantiate a class and use any of its interfaces, it's best to not refer to it via an object-reference type. You've violated that advice by changing your variables to be of type TMyClass instead of an interface type. Declare them as interface variables instead; I'd use IUnknown.

The reason for this advice is that object references are not treated the same as interface references. The compiler always inserts reference-counting code for interface variables, and that code is oblivious to any object references anywhere else in your program. Due to reference counting, an object-reference variable could become invalid after changes to some interface variable, and it's easy to overlook that while writing programs. If you never have an object-reference variable, then you don't need to worry about that possibility; an interface reference should always be valid.

If MyClass1 is an object reference, then you should not call Free on it after you've assigned it to an interface variable. Here's some of your code, annotated with the object's reference count:

MyClass1 := TMyClass.Create;  // initialized to 0

bSupports := Supports(MyClass1,IFirstInterface,i1); // incremented to 1
if bSupports then 
begin
    Memo1.Lines.Add('MyClass1 supports IFirstInterface');
    DoSomethingWith(i1);
end
else
    Memo1.Lines.Add('MyClass1 does not support IFirstInterface');

bSupports := Supports(MyClass1,ISecondInterface,i2); // incremented to 2
if bSupports then 
begin
    Memo1.Lines.Add('MyClass1 supports ISecondInterface');
    DoSomethingElseWith(i2);
end
else
    Memo1.Lines.Add('MyClass1 does not support ISecondInterface');

MyClass1 := nil; // still 2
i1 := nil; // decremented to 1
i2 := nil; // decremented to 0; the object gets destroyed

If you were to call MyClass1.Free at any point, your program would crash. Freeing the object yourself would not change the values in i1 or i2, so the compiler's automatically inserted reference-counting code would still execute. It would attempt to reduce the reference count of an already-freed object, which is obviously not good.

But suppose you waited until after you cleared i1 and i2, as in this code:

i1 := nil;
i2 := nil;
MyClass1.Free;

That's still wrong. Clearing the variables sets the reference count to 0, so the object gets destroyed upon assigning to i2; the value in MyClass1 is invalid, so you shouldn't call Free on it there, either.

The safest thing to do, once you've assigned an object reference to an interface reference, is to clear the object reference immediately. Then you won't be tempted to use it anymore.

There is typically no need to clear an interface variable. It gets cleared automatically at the end of its lifetime (which for local variables is when they go out of scope at the end of the function). Furthermore, if you call Supports and pass in an already-assigned interface reference, it will either receive an interface reference to the new object, or it will be cleared; it will not continue holding its previous value.

That is, when you call Supports(MyClass2,IFirstInterface,i1);, there was no need to clear i1 first. The call to Supports will either fill i1 with a reference to the IFirstInterface for the object referenced by MyClass2, or it will store nil in i1.

OTHER TIPS

In principal...I agree with everything everyone is saying about mixing Interface and Object model and don't mix them...

It's an easy way to get yourself in trouble and doing late night tracking of weird GPF's...

But...(there's always a but)...

You can do it...if you understand the Gotcha's...

  • If you create an TInterfacedObject and pass it to a variable reference. If you need to Free it or not depends on whether you get an Interface from it.
  • Once you get an Interface from it...Reference counting starts...and you are no longer in charge of freeing it. This is true for any object that descends from TInterfacedObject
  • "As" increments the reference count of your interface for as long as the "As" is in scope. Which means if you don't have another interface reference...your object is going bye bye as soon as the "As" drops out of scope.

Checkout Button4Click and Button5Click...Button4Click does it by Interface...Button5Click does by both.

  IReqBase = Interface(IInterface)
  ['{B71BD1C3-CE4C-438A-8090-DA6AACF0B3C4}']
    procedure FillWithTemplateData;
  end;

  IReqLogIn = Interface(IInterface)
  ['{133D2DFF-670C-4942-A81C-D18CBE825A93}']
    procedure SetupPassword(aUserName, aPassword: string);
  end;

  type
   TWebAct = (ttlogin,
              ttsignin);

  TForm59 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    CheckBox1: TCheckBox;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
  private
    { Private declarations }
    FReqList: Array of IReqBase;
    procedure CreateBaseList;
    procedure ClearBaseList;
  public
    { Public declarations }
  end;

  TJSONStructure = class(TInterfacedObject);

  TReqBaseClass = class of TReqBase;

  TReqBase = class(TJSONStructure, IReqBase)
  private
     token: Int64;
  protected
    class function ReqClass: TReqBaseClass; virtual; abstract;
  public
     Constructor Create; virtual;
     procedure FillWithTemplateData; virtual;
     class function ReqBase: IReqBase;
  end;

  TReqLogin = class(TReqBase, IReqLogIn)
  private
    Fusername,
    Fpassword: String;
    Fmodule  : Integer;
  protected
    class function ReqClass: TReqBaseClass; override;
  public
     Constructor Create; override;
     Destructor Destroy; override;
     procedure SetupPassword(aUserName, aPassword: string);
     procedure FillWithTemplateData; override;
  end;

  TReqSignIn = class(TReqBase)
  private
    Fusername,
    Fpassword: String;
    Fmodule  : Integer;
  protected
    class function ReqClass: TReqBaseClass; override;
  public
     Constructor Create; override;
     Destructor Destroy; override;
     procedure FillWithTemplateData; override;
  end;


var
  Form59: TForm59;

implementation

{$R *.dfm}

procedure TForm59.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Clear;

  IReqBase(FReqList[integer(CheckBox1.Checked)]).FillWithTemplateData;
end;

procedure TForm59.Button2Click(Sender: TObject);
begin
  CreateBaseList;
end;

procedure TForm59.Button3Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    TReqSignIn.ReqBase.FillWithTemplateData
  else
    TReqLogin.ReqBase.FillWithTemplateData;
end;

procedure TForm59.Button4Click(Sender: TObject);
var
  a_IReqBase1: IReqBase;
  a_IReqBase2: IReqBase;
  a_IReqLogIn: IReqLogIn;
begin
  a_IReqBase1 := TReqSignIn.Create;
  a_IReqBase2 := TReqLogin.Create;

  a_IReqLogIn :=  a_IReqBase2 as IReqLogIn;
  a_IReqLogIn.SetupPassword('houseofdexter', 'dexter');

  a_IReqBase1.FillWithTemplateData;
  a_IReqBase2.FillWithTemplateData;
  a_IReqLogIn :=  a_IReqBase2 as IReqLogIn;
  a_IReqLogIn.SetupPassword('houseofdexter', 'dexter');
end;

procedure TForm59.Button5Click(Sender: TObject);
var
  a_ReqBase1: TReqSignIn;
  a_ReqBase2: TReqLogin;
  a_IReqBase1: IReqBase;
  a_IReqBase2: IReqBase;
  a_IReqLogIn: IReqLogIn;
begin
  a_ReqBase1 := TReqSignIn.Create;
  a_ReqBase2 := TReqLogin.Create;

  a_IReqBase1 :=  a_ReqBase1;
  a_IReqBase2 :=  a_ReqBase2;

  a_IReqLogIn :=  a_IReqBase2 as IReqLogIn;
  //note we are working with the object...
  a_ReqBase2.SetupPassword('houseofdexter', 'dexter');

  a_IReqBase1.FillWithTemplateData;
  a_IReqBase2.FillWithTemplateData;
  a_IReqLogIn :=  a_IReqBase2 as IReqLogIn;
  a_IReqLogIn.SetupPassword('houseofdexter', 'dexter');
end;

procedure TForm59.ClearBaseList;
begin
  SetLength(FReqList, 0);
end;

procedure TForm59.CreateBaseList;
begin
  if High(FReqList) = Ord(High(TWebAct)) +1 then
    ClearBaseList;

  SetLength(FReqList, Ord(High(TWebAct)) + 1 );
  FReqList[ord(ttlogin)] := TReqLogin.ReqBase;
  FReqList[ord(ttsignin)] := TReqSignIn.ReqBase;
end;

procedure TForm59.FormCreate(Sender: TObject);
begin
  CreateBaseList;
end;

procedure TForm59.FormDestroy(Sender: TObject);
begin
  ClearBaseList;
end;

{ TReqLogin }

constructor TReqLogin.Create;
begin
  inherited;
  FUserName := 'Rick';
  FPassword := 'Test';
  Fmodule := 100;
end;


destructor TReqLogin.Destroy;
begin
  Form59.Memo1.Lines.Add('Destroyed: ' +ClassName);
  Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
end;

procedure TReqLogin.FillWithTemplateData;
begin
  inherited;
  Form59.Memo1.Lines.Add(Fusername);
  Form59.Memo1.Lines.Add(FPassword);
  Form59.Memo1.Lines.Add(IntToStr(FModule));
end;

class function TReqLogin.ReqClass: TReqBaseClass;
begin
  Result := TReqLogin;
end;

procedure TReqLogin.SetupPassword(aUserName, aPassword: string);
begin
  Fusername := aUserName;
  Fpassword := aPassword;
end;

{ TReqBase }

constructor TReqBase.Create;
begin
  inherited;
  Form59.Memo1.Lines.Add('Created: ' +ClassName);
  Token := -1;
end;

procedure TReqBase.FillWithTemplateData;
begin
  Form59.Memo1.Lines.Add(Self.ClassName);
  Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
  Form59.Memo1.Lines.Add(IntToStr(Token));
end;

class function TReqBase.ReqBase: IReqBase;
begin
  Result := ReqClass.Create;
end;

{ TReqSignIn }

constructor TReqSignIn.Create;
begin
  inherited;
  Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
  FUserName := 'Peterson';
  FPassword := 'TestPW';
  Fmodule := 101;
end;

destructor TReqSignIn.Destroy;
begin
  Form59.Memo1.Lines.Add('Destroyed: ' +ClassName);
  Form59.Memo1.Lines.Add('RefCount: ' + IntToStr(TInterfacedObject(Self).RefCount));
  inherited;
end;

procedure TReqSignIn.FillWithTemplateData;
begin
  inherited;
  Form59.Memo1.Lines.Add(Fusername);
  Form59.Memo1.Lines.Add(FPassword);
  Form59.Memo1.Lines.Add(IntToStr(FModule));
end;

class function TReqSignIn.ReqClass: TReqBaseClass;
begin
  Result := TReqSignIn;
end;

end.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top