在我看来,IList不能将事件处理程序作为其元素.该程序在PROGRAM退出时具有访问冲突$ C00000005.
如果我使用Delphi RTL的TList,一切都很好.
32位和64位构建都会发生访问冲突.当它发生时,它似乎停在Spring4D的以下几行:
procedure TCollectionBase<T>.Changed(const item: T; action:
TCollectionChangedAction);
begin
if fOnChanged.CanInvoke then
fOnChanged.Invoke(Self, item, action);
end;
Run Code Online (Sandbox Code Playgroud)
以下示例程序可以在Windows上使用RAD Studio Tokyo 10.2.3复制访问冲突.
program Test_Spring_IList_With_Event_Handler;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
Spring.Collections;
type
TSomeEvent = procedure of object;
TMyEventHandlerClass = class
procedure SomeProcedure;
end;
TMyClass = class
private
FEventList: IList<TSomeEvent>;
public
constructor Create;
destructor Destroy; override;
procedure AddEvent(aEvent: TSomeEvent);
end;
procedure TMyEventHandlerClass.SomeProcedure;
begin
// Nothing to do.
end;
constructor TMyClass.Create;
begin
inherited;
FEventList := TCollections.CreateList<TSomeEvent>;
end;
destructor TMyClass.Destroy;
begin
FEventList := nil;
inherited;
end;
procedure TMyClass.AddEvent(aEvent: TSomeEvent);
begin
FEventList.Add(aEvent);
end;
var
MyEventHandlerObj: TMyEventHandlerClass;
MyObj: TMyClass;
begin
MyObj := TMyClass.Create;
MyEventHandlerObj := TMyEventHandlerClass.Create;
try
MyObj.AddEvent(MyEventHandlerObj.SomeProcedure);
finally
MyObj.Free;
MyEventHandlerObj.Free;
end;
end.
Run Code Online (Sandbox Code Playgroud)
Dav*_*nan 11
这是一个影响泛型的编译器缺陷.TMyClass实例的生命周期实际上并不相关.编译器无法处理的代码TList<T>.DeleteRangeInternal在Spring.Collections.Lists.这段代码:
if doClear then
Changed(Default(T), caReseted);
Run Code Online (Sandbox Code Playgroud)
请记住,这T是一个方法指针,它是一个带有两个指针的类型.因此它比寄存器大.编译器将调用转换Changed为:
Spring.Collections.Lists.pas.641: Changed(Default(T), caReseted); 00504727 B105 mov cl,$05 00504729 33D2 xor edx,edx 0050472B 8B45FC mov eax,[ebp-$04] 0050472E 8B18 mov ebx,[eax] 00504730 FF5374 call dword ptr [ebx+$74]
请注意,编译器只将4个字节归零,然后将这4个字节传递给Changed.
但是,另一方面是实现Changed,其访问item传递的代码如下所示:
Spring.Collections.Base.pas.1583: fOnChanged.Invoke(Self, item, action); 00502E58 FF750C push dword ptr [ebp+$0c] 00502E5B FF7508 push dword ptr [ebp+$08] 00502E5E 8D55F0 lea edx,[ebp-$10] 00502E61 8B45FC mov eax,[ebp-$04] 00502E64 8B4024 mov eax,[eax+$24] 00502E67 8B08 mov ecx,[eax] 00502E69 FF513C call dword ptr [ecx+$3c]
asm代码的前两行从堆栈中读取方法指针.因此,方法指针参数的ABI是它们在堆栈上传递的.这记录如下:
方法指针作为两个32位指针在堆栈上传递.在方法指针之前推送实例指针,以便方法指针占用最低地址.
回到调用此函数的代码.它在一个登记册中传递了这个论点.这种不匹配是导致异常的原因,实际上很久以后就会发生.但这就是一切都在南方的地方.
我们来看一个解决方法.我们将代码更改为TList<T>.DeleteRangeInternal:
var
defaultItem: T;
....
if doClear then
begin
defaultItem := Default(T);
Changed(defaultItem, caReseted);
end;
Run Code Online (Sandbox Code Playgroud)
现在生成的代码是这样的:
Spring.Collections.Lists.pas.643: defaultItem := Default(T); 0050472B 33C0 xor eax,eax 0050472D 8945E0 mov [ebp-$20],eax 00504730 8945E4 mov [ebp-$1c],eax Spring.Collections.Lists.pas.644: Changed(defaultItem, caReseted); 00504733 FF75E4 push dword ptr [ebp-$1c] 00504736 FF75E0 push dword ptr [ebp-$20] 00504739 B205 mov dl,$05 0050473B 8B45FC mov eax,[ebp-$04] 0050473E 8B08 mov ecx,[eax] 00504740 FF5174 call dword ptr [ecx+$74]
请注意,生成此时间代码以将方法指针中的两个指针归零,然后通过堆栈传递它们.此调用代码与被调用者的代码匹配.一切都很好.
我将这个变通方法提交给我的persomal Spring4D repo,Stefan将它合并到主repo上的1.2.2 hotfix分支.
我提交了一份错误报告:RSP-20683.
| 归档时间: |
|
| 查看次数: |
258 次 |
| 最近记录: |