Delphi:使用 RTTI 从 TMemo.Text 设置对象属性的非确定性访问冲突
我正在构建一个非常粗糙的 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(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)。原因是什么?我想我永远不会知道。
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.