Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/delphi/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Delphi 这是Rio上System.Net.HttpClient中的错误吗?_Delphi_Firemonkey_Delphi 10.3 Rio - Fatal编程技术网

Delphi 这是Rio上System.Net.HttpClient中的错误吗?

Delphi 这是Rio上System.Net.HttpClient中的错误吗?,delphi,firemonkey,delphi-10.3-rio,Delphi,Firemonkey,Delphi 10.3 Rio,这是Delphi Rio中的函数,位于System.Net.HttpClient THTTPClientHelper = class helper for THTTPClient .... procedure THTTPClientHelper.SetExt(const Value); var {$IFDEF AUTOREFCOUNT} LRelease: Boolean; {$ENDIF} LExt: THTTPClientExt; begin if FHTTPClientLis

这是Delphi Rio中的函数,位于
System.Net.HttpClient

THTTPClientHelper = class helper for THTTPClient
....

procedure THTTPClientHelper.SetExt(const Value);
var
{$IFDEF AUTOREFCOUNT}
  LRelease: Boolean;
{$ENDIF}
  LExt: THTTPClientExt;
begin
  if FHTTPClientList = nil then
    Exit;
  TMonitor.Enter(FHTTPClientList);
  try
{$IFDEF AUTOREFCOUNT}
    LRelease := not FHTTPClientList.ContainsKey(Self);
{$ENDIF}
    LExt := THTTPClientExt(Value);
    FHTTPClientList.AddOrSetValue(Self, LExt);
{$IFDEF AUTOREFCOUNT}
    if LRelease then __ObjRelease;
{$ENDIF}
  finally
    TMonitor.Exit(FHTTPClientList);
  end;
end;
这家伙想用
LRelease
做什么

{$IFDEF AUTOREFCOUNT}
    LRelease := not FHTTPClientList.ContainsKey(Self);
{$ENDIF}
    LExt := THTTPClientExt(Value);
    FHTTPClientList.AddOrSetValue(Self, LExt);
{$IFDEF AUTOREFCOUNT}
    if LRelease then __ObjRelease;
{$ENDIF}
因此,如果
FHTTPClientList
不包含
THTTPClient
则将其添加到
FHTTPClientList
中,然后将其refcount减少1。为什么要将其refcount减少一个??
THTTPClient
仍处于活动状态,为什么要破坏它的refcount?他们在这里是一个bug,可能是那个家伙打错了,但我不明白他最初想做什么

有关如何从字典中删除项的信息:

procedure THTTPClientHelper.RemoveExt;
begin
  if FHTTPClientList = nil then
    Exit;
  TMonitor.Enter(FHTTPClientList);
  try
    FHTTPClientList.Remove(Self);
  finally
    TMonitor.Exit(FHTTPClientList);
  end;
end;

上述代码中ARC编译器手动引用计数的目的是模拟具有弱引用的字典。Delphi泛型集合由泛型数组支持,泛型数组将在ARC编译器上保存对添加到集合中的任何对象的强引用

有几种方法可以实现弱引用—使用指针,在对象被声明为弱引用的位置使用包装器,以及在适当的位置手动计算引用

使用指针会失去类型安全性,包装器需要更多的代码,所以我猜上面代码的作者选择了手动引用计数。那部分没问题

但是,正如您所注意到的,该代码中有一些可疑之处—虽然
SetExt
例程编写正确,但
RemoveExt
有一个错误,导致以后崩溃

让我们在ARC编译器的上下文中浏览代码(为了简洁起见,我将省略编译器指令和无关的代码):

由于将对象添加到集合(数组)中会增加引用计数,为了实现弱引用,我们必须减少添加的对象实例的引用计数-这样实例的引用计数在存储到集合中后将保持不变。接下来,当我们从这样的集合中移除对象时,我们必须恢复引用计数平衡并增加引用计数。此外,我们还必须确保在销毁对象之前将其从此类集合中删除—这样做的好地方是析构函数

添加到集合:

LRelease := not FHTTPClientList.ContainsKey(Self);
FHTTPClientList.AddOrSetValue(Self, LExt);
if LRelease then __ObjRelease;
if FHTTPClientList.ContainsKey(Self) then
  begin
    __ObjAddRef;
    FHTTPClientList.Remove(Self);
  end;
destructor THTTPClient.Destroy;
begin
  RemoveExt;
  inherited;
end;
我们将对象添加到集合中,然后在集合持有对对象的强引用后,我们可以释放它的引用计数。如果对象已经在集合中,这意味着它的引用计数已经减少,我们不能再减少它-这就是
LRelease
标志的目的

从收藏中删除:

LRelease := not FHTTPClientList.ContainsKey(Self);
FHTTPClientList.AddOrSetValue(Self, LExt);
if LRelease then __ObjRelease;
if FHTTPClientList.ContainsKey(Self) then
  begin
    __ObjAddRef;
    FHTTPClientList.Remove(Self);
  end;
destructor THTTPClient.Destroy;
begin
  RemoveExt;
  inherited;
end;
如果对象在集合中,我们必须在从集合中删除对象之前恢复平衡并增加引用计数。这是
RemoveExt
方法中缺少的部分

确保销毁时对象不在列表中:

LRelease := not FHTTPClientList.ContainsKey(Self);
FHTTPClientList.AddOrSetValue(Self, LExt);
if LRelease then __ObjRelease;
if FHTTPClientList.ContainsKey(Self) then
  begin
    __ObjAddRef;
    FHTTPClientList.Remove(Self);
  end;
destructor THTTPClient.Destroy;
begin
  RemoveExt;
  inherited;
end;
注意:为了使这种伪造的弱集合能够正常工作,必须仅通过上述方法添加和删除项目,这些方法负责平衡引用计数。使用任何其他原始采集方法(如
Clear
)将导致引用计数中断


是否存在Bug?

System.Net.HttpClient
code-breaked
RemoveExt
中,方法仅在析构函数中调用,而且
FHTTPClientList
是私有变量,不会以任何其他方式更改。乍一看,该代码工作正常,但实际上包含相当微妙的bug

要解开真正的bug,我们需要涵盖可能的使用场景,从几个已确定的事实开始:

  • FHTTPClientList
    字典中,只有
    SetExt
    RemoveExt
    方法可以更改内容和引用项计数
  • SetExt
    方法正确
  • 不调用
    \uuu ObjAddRef
    的breaked
    RemoveExt
    方法只在
    THTTPClient
    析构函数中调用,这就是这个微妙的错误的根源
  • 当对任何特定对象实例调用析构函数时,这意味着对象实例已达到其生存期,并且任何后续引用计数触发器(在析构函数执行期间)都不会影响代码的正确性

    这是通过在
    FRefCount
    变量上应用
    objDestroyingFlag
    来确保的,更改其值和任何进一步增加/减少的计数将不再导致启动销毁过程的特殊值
    0
    ,因此对象是安全的,不会被销毁两次

    在上面的代码中,当调用
    THTTPClient
    析构函数时,这意味着对对象实例的最后一个强引用已超出范围或被设置为
    nil
    ,此时唯一剩余的能够触发引用计数机制的活动引用是
    FHTTPClientList
    中的引用。该引用已被
    RemoveExt
    方法清除(断开或不断开),如前所述,这无关紧要。一切正常

    但是,代码的作者忘记了触发析构函数的一个很小的方法-
    dispeof
    ,但此时对象实例还没有达到其引用计数生存期。换句话说,如果析构函数是由
    dispeof
    调用的,则任何后续的引用计数触发器都必须平衡,因为在析构函数链调用完成后,仍有对对象的活动引用将触发引用计数机制。如果我们在这一点上打破计数,结果将是灾难性的

    由于
    THTTPClient
    不是
    t组件
    的后代,因此需要
    处理
    很容易疏忽,忘记某人,无论如何,某个地方都可以对此类变量调用
    DipsoseOf
    ——例如,如果您创建了
    THTTPClient
    实例的自有列表,则清除此类列表将