【问题标题】:Multiple interfaces, Supports() function and reference counting多个接口,Supports() 函数和引用计数
【发布时间】:2013-08-11 11:25:35
【问题描述】:

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

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. 我应该 Free() 还是 'nil' MyClass1 或者甚至不理会它?

  3. 在处理完 2. 之后,关于引用计数是否仍需要两条 ix:= nil 语句?

【问题讨论】:

  • (1) 是的,(2) 你创建了它并且没有使用 addRef;等等,是的Free 它,(3)是的,您将它们用作接口对象。现在,如果您将 lMyClass1 分配给一个接口,那么 不要 释放它;你只是把它放到引用地里
  • 你所做的一切都很好......你必须注意什么。 DoSomethingWith(MyClass2 as IFirstInterface);您将混合模型...“As” Ref 将您的 MyClass2 计数加 1,然后当 DoSomethingWith 完成时,它 Ref 计数回减 0...如果 ref count = 0,则 free 是您的对象。跨度>
  • 应该读作“完成它Ref倒计时1”
  • 您应该知道的事情...如果您知道您的对象支持该接口,则不必使用 Support。你可以只写 i1 := MyClass2。编译器将确保您的对象支持该接口。
  • 您正在混合类引用和接口引用。这是一个很大的禁忌。您不需要以这种方式使用 Supports。只需将所有变量声明为接口,编译器就会告诉您是否正确布局。不要让自己难过。

标签: delphi interface reference-counting


【解决方案1】:

原则上...我同意每个人所说的关于混合接口和对象模型的一切,不要混合它们...

这是一种让自己陷入困境并在深夜跟踪奇怪的 GPF 的简单方法......

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

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

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

Checkout Button4Click 和 Button5Click...Button4Click 由 Interface...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.

【讨论】:

    【解决方案2】:

    一个常见的建议是永远不要将对象引用与接口引用混合。这意味着如果您需要实例化一个类并使用它的任何接口,最好通过对象引用类型来引用它。您违反了该建议,将变量更改为 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 的调用将使用对IFirstInterface 的引用填充i1,以供MyClass2 引用的对象,或者它将nil 存储在i1 中。

    【讨论】:

    • MyClass1 := nil; // 还是 2 i1 := nil; // 递减到 1 i2 := nil; // 递减到 0;对象被销毁 MyClass1.Free;很好......因为他将 Object 设置为 nil......并且 Free 在调用析构函数之前检查 Self 是否为 nil......我原则上同意你......如果你想要接口坚持使用接口......但是他们是想要使用对象并且您不想担心清理代码的时候...因此您可以创建对象...将其传递给接口...然后知道它将被销毁当引用消失时。
    • 这是真的,@House,但我从来没有打算建议第一行(清除MyClass1)将保留在我在回答中显示的三行中。在MyClass1 上调用Free nil 分配给它在所有 情况下都是没有意义的,无论i1i2 发生什么,所以为什么还要考虑呢?它没有增加讨论。如果您不想担心清理代码,那么您应该取出清理代码,因为它只会让事情变得更糟。一旦一个对象被接口变量所拥有,就不要对它使用像Free这样的对象管理方法。
    • 我同意...但是如果留在里面它不会做任何事情...重要的是...一旦您获得接口...您不再负责释放它...事实上,就像我在我的代码示例中展示的那样...如果您了解自己在做什么,您可以轻松混合两种模型...显然实施者期望模型混合,否则为什么我们有 TObject。获取接口。
    猜你喜欢
    • 2015-05-14
    • 2011-05-24
    • 2010-09-15
    • 2014-05-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多