多个接口,Supports()函数和引用计数

时间:2013-08-09 13:48:59

标签: delphi interface reference-counting

如果我理解正确,这很好:

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);

现在我有一个实现多个接口的类:

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;

我可以调用另一个重载的Support()返回接口并对其执行某些操作):

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');

我有三个问题:

  1. MyClass1, MyClass2现在是对象类型,而不是简单示例中的接口类型。这样可以吗?

  2. 我应该释放()或“无”MyClass1,甚至可能不管它吗?

  3. 在处理完2.之后,仍然需要关于引用计数的两个ix:= nil语句吗?

2 个答案:

答案 0 :(得分:4)

一个常见的建议是永远不要将对象引用与接口引用混合在一起。这意味着如果你需要实例化一个类并使用它的任何接口,那么最好通过对象引用类型来引用它。您通过将变量更改为TMyClass类型而不是接口类型来违反该建议。将它们声明为接口变量;我会使用IUnknown

这个建议的原因是对象引用不被视为与接口引用相同。编译器总是为接口变量插入引用计数代码,并且该代码对程序中任何其他位置的任何对象引用都是无关紧要的。由于引用计数,对象引用变量在更改某些接口变量后可能会变为无效,并且在编写程序时很容易忽略。如果你从未有过对象引用变量,那么你不必担心这种可能性;接口引用始终有效。

如果MyClass1是对象引用,则在将其分配给接口变量后,会在其上调用Free。这是你的一些代码,用对象的引用计数注释:

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

如果您在任何时候致电MyClass1.Free,您的计划将会崩溃。自己释放对象不会更改i1i2中的值,因此编译器自动插入的引用计数代码仍将执行。它会尝试减少已经释放的对象的引用计数,这显然不是很好。

但是假设您等到之后,您已清除i1i2,如下代码所示:

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

那还是错的。清除变量会将引用计数设置为0,因此在分配给i2时对象将被销毁; MyClass1中的值无效,因此您不应在其上调用Free

最安全的做法是,一旦为对象引用分配了对象引用,就要立即清除对象引用。那么你就不会再想要使用它了。

通常不需要清除接口变量。它在生命周期结束时自动清除(对于局部变量,它们在函数结束时超出范围)。此外,如果您调用Supports并传入已分配的接口引用,它将接收对新对象的接口引用,或者它将被清除;它不会继续保持其先前的价值。

也就是说,当您致电Supports(MyClass2,IFirstInterface,i1);时,无需先清除i1。对Supports的调用将填充i1,引用IFirstInterface引用MyClass2引用的对象,或者将nil存储在i1中}}

答案 1 :(得分:0)

原则上......我同意每个人都在谈论混合接口和对象模型的所有内容,不要混用它们......

这是让你自己陷入困境,并对奇怪的GPF进行深夜追踪的简单方法......

但是......(总有一个但是......)

你可以做到......如果你理解了Gotcha的......

  • 如果您创建TInterfacedObject并将其传递给变量引用。如果你需要释放它取决于你是否从它获得一个接口。
  • 一旦你从它获得一个接口...引用计数开始......你不再负责释放它。对于任何来自TInterfacedObject
  • 的对象都是如此
  • 只要“As”在范围内,“As”就会增加接口的引用计数。这意味着如果你没有其他的界面引用......只要“As”退出范围,你的对象就会再见。

Checkout Button4Click和Button5Click ... Button4Click通过界面完成... Button5Click由两者完成。

  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.