Delphi:使用 RTTI 从 TMemo.Text 设置对象属性的非确定性访问冲突

Delphi: non-deterministic access violation using RTTI to set object properties from TMemo.Text

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

免责声明:对于臃肿的代码示例感到抱歉。这里是:

表格。

1
2
3
4
5
6
7
8
9
{ 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;

数据模型。

1
2
3
4
5
6
7
8
9
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;

实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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),则不会引发访问冲突。

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


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

重新启动 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)。原因?我想我永远不会知道。