Delphi 编译器什么时候创建隐式接口变量?
When does the Delphi compiler create an implicit interface variable?
我在 Whosebug 上测试一段代码以获取评论,我遇到了隐式接口变量抬头的情况。在这种情况下,我看不到是什么原因造成的。我有一个迷你工厂,它为新创建的对象返回一个接口。如果我在过程块中调用该方法,那么我只会得到 1 的引用计数。如果我从主程序块调用它,那么我会得到 2 的引用计数。
我用 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-]
005C6A5D 8B45FC mov eax,[ebp-]
005C6A60 E83BFEFFFF call TMyFactory.CreateMyInt
TestRefCount.dpr.68: Assert(myInt.GetRefCount = 1);
005C6A65 8B45F8 mov eax,[ebp-]
这是我们可以看到隐式接口变量的第二个块:
TestRefCount.dpr.86: myInt := myFactory.CreateMyInt;
005CF513 8D55EC lea edx,[ebp-]
005CF516 A19CB75D00 mov eax,[[=13=]5db79c]
005CF51B E88073FFFF call TMyFactory.CreateMyInt
005CF520 8B55EC mov edx,[ebp-]
005CF523 B8A0B75D00 mov eax,[=13=]5db7a0
005CF528 E8C7E3E3FF call @IntfCopy
TestRefCount.dpr.87: Assert(myInt.GetRefCount = 1); // This fails because the refcount is 2
005CF52D A1A0B75D00 mov eax,[[=13=]5db7a0]
据我所知,编译器对局部变量和全局变量的处理方式不同,而且在任何文档中都没有写下来。对于本地,它相信对本地的分配会成功。因此不需要隐式本地。那是你的第一个案例。
对于全局变量,编译器更加谨慎。它以怀疑的眼光看待全局变量。它会在变量赋值失败时发出防御代码。编译器首先分配给一个隐式本地,一个它肯定会成功的分配。因此,接口将增加其引用计数。然后它分配给全局。如果失败,那么至少隐式本地有一个引用并且能够递减它并正确释放接口。
您可能想知道为什么编译器对分配给您的全局变量感到紧张。你知道它是安全的,不会失败,编译器怕什么?它的全局变量概念更广泛。例如,它会将指向接口引用的指针视为全局指针。编译器试图防止该指针无效、赋值失败以及没有人引用该接口。编译器只考虑两种情况:局部和全局。它信任对局部变量的赋值,而其他一切都与具有潜在风险的全局变量混为一谈。包括您绝对安全的全球。
在我看来,编译器过于谨慎了。如果程序员说可以给变量赋值,我觉得不是编译器怀疑的地方。如果程序员犯了错误,那么程序员当然应该准备好接受后果。无论是泄漏还是运行时内存访问失败。但设计师们采取了一种不同的、更保守的方法。
您看到隐式局部变量的另一种情况是在函数 return 值上使用 as
运算符。例如:
Foo := GetBar as IFoo;
更多信息请点击此处:The mysterious case of the unexpected implicit interface variable。
虽然这个案子很明确。隐式局部变量是必不可少的,因为 as
引发异常是完全合理的。
我在 Whosebug 上测试一段代码以获取评论,我遇到了隐式接口变量抬头的情况。在这种情况下,我看不到是什么原因造成的。我有一个迷你工厂,它为新创建的对象返回一个接口。如果我在过程块中调用该方法,那么我只会得到 1 的引用计数。如果我从主程序块调用它,那么我会得到 2 的引用计数。
我用 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-]
005C6A5D 8B45FC mov eax,[ebp-]
005C6A60 E83BFEFFFF call TMyFactory.CreateMyInt
TestRefCount.dpr.68: Assert(myInt.GetRefCount = 1);
005C6A65 8B45F8 mov eax,[ebp-]
这是我们可以看到隐式接口变量的第二个块:
TestRefCount.dpr.86: myInt := myFactory.CreateMyInt;
005CF513 8D55EC lea edx,[ebp-]
005CF516 A19CB75D00 mov eax,[[=13=]5db79c]
005CF51B E88073FFFF call TMyFactory.CreateMyInt
005CF520 8B55EC mov edx,[ebp-]
005CF523 B8A0B75D00 mov eax,[=13=]5db7a0
005CF528 E8C7E3E3FF call @IntfCopy
TestRefCount.dpr.87: Assert(myInt.GetRefCount = 1); // This fails because the refcount is 2
005CF52D A1A0B75D00 mov eax,[[=13=]5db7a0]
据我所知,编译器对局部变量和全局变量的处理方式不同,而且在任何文档中都没有写下来。对于本地,它相信对本地的分配会成功。因此不需要隐式本地。那是你的第一个案例。
对于全局变量,编译器更加谨慎。它以怀疑的眼光看待全局变量。它会在变量赋值失败时发出防御代码。编译器首先分配给一个隐式本地,一个它肯定会成功的分配。因此,接口将增加其引用计数。然后它分配给全局。如果失败,那么至少隐式本地有一个引用并且能够递减它并正确释放接口。
您可能想知道为什么编译器对分配给您的全局变量感到紧张。你知道它是安全的,不会失败,编译器怕什么?它的全局变量概念更广泛。例如,它会将指向接口引用的指针视为全局指针。编译器试图防止该指针无效、赋值失败以及没有人引用该接口。编译器只考虑两种情况:局部和全局。它信任对局部变量的赋值,而其他一切都与具有潜在风险的全局变量混为一谈。包括您绝对安全的全球。
在我看来,编译器过于谨慎了。如果程序员说可以给变量赋值,我觉得不是编译器怀疑的地方。如果程序员犯了错误,那么程序员当然应该准备好接受后果。无论是泄漏还是运行时内存访问失败。但设计师们采取了一种不同的、更保守的方法。
您看到隐式局部变量的另一种情况是在函数 return 值上使用 as
运算符。例如:
Foo := GetBar as IFoo;
更多信息请点击此处:The mysterious case of the unexpected implicit interface variable。
虽然这个案子很明确。隐式局部变量是必不可少的,因为 as
引发异常是完全合理的。