1

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?

7
  • (1) Yes, (2) You created it and didn't use addRef; etc so yes Free it, (3) Yes, you're using them as Interfaced objects. Now if you assigned lMyClass1 to an interface, then do not free it; you just put it into refcounting land Commented Aug 9, 2013 at 13:58
  • Everything you've done is fine...What you have to look out for. DoSomethingWith(MyClass2 as IFirstInterface); You would be mixing models..."As" Ref counts your MyClass2 up by 1 and then when DoSomethingWith is finished it Ref counts back down by 0...and if the ref count = 0 then free's the object for you. Commented Aug 9, 2013 at 14:32
  • should read "is finished it Ref counts back down by 1" Commented Aug 9, 2013 at 14:39
  • Things you should know...You don't have to use Support if you know your object supports that interface. You can just write i1 := MyClass2. The compiler will make sure that interface is supported by your object. Commented Aug 9, 2013 at 14:54
  • You are mixing class references and interface references. That's a big no-no. You don't need to use Supports in that way. Just declare all your variables as interfaces, and the compiler will tell you if you have things laid out properly or not. Don't make things hard on yourself. Commented Aug 9, 2013 at 19:11

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

3 Comments

MyClass1 := nil; // still 2 i1 := nil; // decremented to 1 i2 := nil; // decremented to 0; the object gets destroyed MyClass1.Free; is fine...because he set the Object to nil...and Free checks if Self is nil before calling the destructor...I do agree with you in principle...if you want interfaces stick with the interface...but their are times when want to work with object and you don't want to worry about cleaning up code...so you can create the object...pass it to an interface...and then know that it will be destroyed when the reference goes away.
That's true, @House, but I never meant to suggest the first line (clearing MyClass1) would be retained in the three lines I show in my answer. Calling Free on MyClass1 after assigning nil to it would be pointless in all cases, no matter what happens to i1 and i2, so why even bother considering it? It adds nothing to the discussion. If you don't want to worry about cleaning up code, then you should take out the cleanup code because it's only going to make things worse. Once an object is owned by interface variables, don't use object-management methods like Free on it at all.
I agree...but it doesn't do anything if left in...what's important...is that as soon as you get an Interface...that you are no longer in charge of freeing it...matter of fact like I show in my code example...you can easily mix both models if you understand what you are doing...obviously the implementers expected the models the mixed otherwise why do we have TObject.GetInterface.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.