【问题标题】:Delphi: non-deterministic access violation using RTTI to set object properties from TMemo.TextDelphi:使用 RTTI 从 TMemo.Text 设置对象属性的非确定性访问冲突
【发布时间】:2011-10-06 12:07:46
【问题描述】:

我正在构建一个非常粗糙的 GUI 来建模映射器,它基本上遍历表单上的所有 TEdit 和 TMemo 字段,提取文本并将此文本设置在数据模型对象中。 (解决方案依赖于我公认的脆弱的“约定优于配置”方法,仅匹配数据模型中与表单中的字段具有相同名称的属性。)

免责声明:对于臃肿的代码示例感到抱歉。如下:

表格。

{ Standard interface section above this line }
type
  TfrmMain = class(TForm)
    StringField1: TEdit;
    StringField2: TMemo;
    { Other fields and procedures dropped for brevity }
  private
    procedure FillGUIFields();
  end;

数据模型。

TDataModel = class(TObject)
private
  FStringField1: string;
  FStringField2: string;
  { Getters and setters dropped for brevity }
public
  property StringField1: string read GetFStringField1 write SetFStringField1;
  property StringField2: string read GetFStringField2 write SetFStringField2;
end;

实施。

const
  MAX_RUNS = 100;

procedure GUIToData(var AObject: TObject; const Form: TForm);
var
  c:          TRTTIContext;
  t:          TRTTIType;
  prop:       TRTTIProperty;
  Component:  TComponent;
  Text:       string;
  i:          integer;
begin
  c := TRTTIContext.Create();
  t := c.GetType(AObject.ClassType);
  for prop in t.GetProperties do begin
    Component := Form.FindComponent(prop.Name); // Naive "conv. over conf." matching
    if (Component <> nil) then begin
      if (Component is TEdit) then prop.SetValue(AObject, TValue.FromVariant(TEdit    (Component).Text));
      if (Component is TMemo) then prop.SetValue(AObject, TValue.FromVariant(TMemo(Component).Text));
    end;
  end;
  c.Free();
end;

procedure TfrmMain.btnFetchToModelClick(Sender: TObject);
var
  Data:               TDataModel;
  i:                  integer;
  NumberOfExceptions: integer;
begin
  NumberOfExceptions := 0;
  for i := 0 to MAX_RUNS - 1 do begin
    try
      FillGUIFields();
      Data := TDataModel.Create();
      GUIToData(TObject(Data), self);
      Data.Free();
    except on E: EAccessViolation do
      begin
        Inc(NumberOfExceptions);
      end;
    end;
  end;
  MessageDlg('Number of runs: ' + IntToStr(MAX_RUNS) + #13#10 +
             'Number of exceptions: ' + IntToStr(NumberOfExceptions), mtInformation, [mbOk], 0);
end;

function TDataModel.GetFStringField1: string;
begin
  Result := FStringField1;
end;

procedure TDataModel.SetFStringField1(Value: string);
begin
  FStringField1 := Value;
end;

{ Identical getter/setter for StringField2 }

procedure TfrmMain.FillGUIFields;
var
  i:          integer;
  TempBuffer: string;
begin
  TempBuffer := '';
  Randomize();
  for i := 0 to Random(16) - 1 do begin
    if Random(2) = 0 then
      TempBuffer := TempBuffer + Chr(Random(25) + 65)
    else
      TempBuffer := TempBuffer + Chr(Random(25) + 97);
  end;
  StringField1.Text := TempBuffer; // Filling the edit field
  { Identical code for filling the memo field }
end;

end.

当我运行它时,在大约 27% 的情况下,我会遇到访问冲突异常。如果我只设置与 TEdit 字段名称匹配的属性(即 StringField1),则不会发生异常。如果我直接访问字段(让 getter/setter 直接指向字段或在 GUIToData 过程中使用 t.GetFields),则不会引发访问冲突。

人们能够重现这个吗?有谁知道是什么导致了这种奇怪的行为?谢谢!

【问题讨论】:

  • 您使用的是哪个版本的 Delphi?
  • 既然您说它在删除 setter 方法时有效,请尝试将“const”添加到 setter 中,如下所示:“SetFStringField1(const Value: string);”
  • @Ville:我正在运行 D2010。 const 建议真的很有趣,但是我无法验证它,因为我现在无法重现 AV。不过,我明天再试一次。
  • 我在 Delphi XE 上运行它,每次都运行良好。显然,您的示例代码中缺少某些内容。 (或者您使用的是 D2010,这是 XE 中修复的错误。)
  • 我刚刚在 D2010 中尝试过,每次都运行良好。

标签: delphi properties delphi-2010 access-violation rtti


【解决方案1】:

好的。所以这就是发生的事情。

重新启动 RAD Studio 并没有解决问题 - 即我仍然能够重现。然后我让一个同事在他的电脑上编译项目,没有出现异常。相同的代码和相同的 RAD Studio 版本(显然 - 我们都运行 D2010 版本 14.0.3513.24210)。然后我让我的同事在他的机器上运行我失败的可执行文件,它的行为与我的计算机上的完全一样。 (我们在十六进制编辑器中比较了他和我电脑中的 exe,结果发现非常明显的差异。)

然后,我们比较了与 D2010 捆绑的 Win32 源。那里也有很多差异,尤其是在 Classes.pas 和 RTTI.pas 中。

我的 D2010 设置一定有问题。解决方案?运行 RAD Studio 2010 Update 4 解决了我的问题(现在运行 D2010 版本 14.0.3593.25826)。原因?我想我永远不会知道。

【讨论】:

    猜你喜欢
    • 2010-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-30
    • 1970-01-01
    相关资源
    最近更新 更多