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