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

发布于 2024-12-08 14:45:46 字数 3027 浏览 0 评论 0原文

我正在构建一个非常粗糙的 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),则不会引发访问冲突。

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

I'm building a really crude GUI to model mapper which basically traverses all TEdit and TMemo fields on a form, extracts the text and set this text in a data model object. (The solution hinges on am admittedly brittle "convention over configuration" approach, matching only the properties in the data model that has identical name with a field in the form.)

Disclaimer: sorry about the bloated code example. Here goes:

The form.

{ 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;

The data model.

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;

The implementation.

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.

When I run this, I get an access violation exception in something like 27 % of the cases. If I only set the property matching the name of the TEdit-field (i.e. StringField1), no exceptions occur. If I access the fields directly (either letting the getter/setter point directly at the fields or using t.GetFields in the GUIToData-procedure), no access violation is thrown.

Are people able to reproduce this? Do anyone know what causes this strange behavior? Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

禾厶谷欠 2024-12-15 14:45:46

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

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

Ok. So here's what's happened.

Restarting RAD Studio did not solve the problem - i.e. I was still able to reproduce. Then I asked a colleague to compile the project on his computer, no exceptions occurred. Same code and same RAD Studio version (apparently - we where both running D2010 Version 14.0.3513.24210). Then I asked my colleague to run my failing executable on his machine, it behaved exactly as on my computer. (We compared the exes from his and my computer in a hex editor, which revealed pretty glaring discrepancies.)

Then, we compared the Win32 sources bundled with D2010. A lot of discrepancies there as well, in particular in Classes.pas and RTTI.pas.

Something had to be wrong with my D2010 setup. Solution? Running RAD Studio 2010 Update 4 solved my problem (now running D2010 Version 14.0.3593.25826). The cause? I guess I'll never know.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文