2017-02-18 40 views
4

我在StackOverflow上测试了一个代码块来评论这里,我遇到了隐式接口变量触发它的情况。我无法看到的是在这种情况下导致它的原因。我有一个小工厂返回一个新创建的对象的接口。如果我在过程块中调用方法,那么我只能得到1的引用计数。如果我从主程序块中调用它,那么我得到的引用计数为2.什么时候Delphi编译器创建一个隐式接口变量?

我使用Delphi 10 Seattle进行了测试。是否存在包含隐式接口创建规则的资源,并且工厂返回的接口不是可靠模式?

program TestRefCount; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    System.SysUtils, 
    Vcl.Dialogs; 

type 
    IMyInterface = interface(IInterface) 
    ['{62EB2C46-9B8A-47CE-A881-DB96E6F6437D}'] 
    procedure DoSomething; 
    function GetRefCount: Integer; 
    end; 

    TMyObject = class(TInterfacedObject, IMyInterface) 
    strict private 
    FMyValue: Integer; 
    public 
    procedure Init; 
    procedure DoSomething; 
    function GetRefCount: Integer; 
    end; 

    TMyFactory = class(TObject) 
    private 
    function CreateMyInt: IMyInterface; 
    end; 

procedure TMyObject.DoSomething; 
begin 
    MessageDlg(IntToStr(FMyValue), mtInformation, [mbok], 0); 
end; 

function TMyObject.GetRefCount: Integer; 
begin 
    Result := FRefCount; 
end; 

procedure TMyObject.Init; 
begin 
    FMyValue := 100; 
end; 

function TMyFactory.CreateMyInt: IMyInterface; 
var 
    myObject: TMyObject; 
begin 
    myObject := TMyObject.Create; 
    Assert(myObject.GetRefCount = 0); 
    myObject.Init; 
    Assert(myObject.GetRefCount = 0); 
    Result := myObject; 
    Assert(myObject.GetRefCount = 1); 
    Assert(Result.GetRefCount = 1); 
end; 

procedure WorkWithIntf; 
var 
    myFactory: TMyFactory; 
    myInt: IMyInterface; 
begin 
    myFactory := TMyFactory.Create; 
    try 
    myInt := myFactory.CreateMyInt; 
    Assert(myInt.GetRefCount = 1); 
    myInt.DoSomething; 
    Assert(myInt.GetRefCount = 1); 
    finally 
    myFactory.Free; 
    end; 
end; 

var 
    myFactory: TMyFactory; 
    myInt: IMyInterface; 
begin 
    try 
    // This case doesn't have an implicit interface variable 
    WorkWithIntf; 
    // This case does have an implicit interface variable 
    myFactory := TMyFactory.Create; 
    try 
     myInt := myFactory.CreateMyInt; 
     Assert(myInt.GetRefCount = 1); // This fails because the refcount is 2 
     myInt.DoSomething; 
     Assert(myInt.GetRefCount = 1); 
    finally 
     myFactory.Free; 
    end; 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
end. 

下面是其中不存在隐式接口变量第一块:

TestRefCount.dpr.67: myInt := myFactory.CreateMyInt; 
005C6A5A 8D55F8   lea edx,[ebp-$08] 
005C6A5D 8B45FC   mov eax,[ebp-$04] 
005C6A60 E83BFEFFFF  call TMyFactory.CreateMyInt 
TestRefCount.dpr.68: Assert(myInt.GetRefCount = 1); 
005C6A65 8B45F8   mov eax,[ebp-$08] 

这里是第二块,其中我们可以看到隐式接口变量:

TestRefCount.dpr.86: myInt := myFactory.CreateMyInt; 
005CF513 8D55EC   lea edx,[ebp-$14] 
005CF516 A19CB75D00  mov eax,[$005db79c] 
005CF51B E88073FFFF  call TMyFactory.CreateMyInt 
005CF520 8B55EC   mov edx,[ebp-$14] 
005CF523 B8A0B75D00  mov eax,$005db7a0 
005CF528 E8C7E3E3FF  call @IntfCopy 
TestRefCount.dpr.87: Assert(myInt.GetRefCount = 1); // This fails because the refcount is 2 
005CF52D A1A0B75D00  mov eax,[$005db7a0] 

回答

0

正如我理解它,并且它不会写在任何文档中,编译器会以不同的方式处理本地和全局变量。有了本地人,它相信分配给当地人会成功。因此不需要隐式本地。这是你的第一个案子。

对于全局变量,编译器更谨慎。它怀疑全局变量。如果赋值给变量失败,它会发出防御性代码。编译器首先分配给一个隐式本地,一个肯定会成功的分配。因此界面的参考计数会增加。然后它分配给全球。如果失败了,那么至少隐式本地有一个引用,并且能够递减它并正确释放接口。

您可能想知道为什么编译器对分配给您的全局变量感到紧张。你知道这是安全的,并且不会失败,编译器害怕什么?其全球变量的概念更广泛。例如,它会将一个指向接口引用的指针视为全局指针。编译器试图防止该指针无效,赋值失败,并且没有人引用该接口。编译器只考虑两种情况:本地和全局。它信任赋值给局部变量,而其他所有变量都会与潜在风险的全局变量混合在一起。包括你完全安全的全球。

在我看来,编译器正在过于谨慎。如果程序员说这个变量可以被赋值,我不认为这是编译器怀疑它的地方。如果程序员犯了一个错误,那么程序员当然应该准备好接受后果。无论是泄漏还是运行时内存访问失败。但设计师采取了另一种更保守的方法。

您看到一个隐式局部变量的另一种情况是在函数返回值上使用as运算符。例如:

Foo := GetBar as IFoo; 

更多关于这里:The mysterious case of the unexpected implicit interface variable

虽然这种情况很明显。隐式局部变量很重要,因为as引发异常是完全合理的。

相关问题