IList<T> падает, когда T является обработчиком событий?
Мне кажется, что IList не может принимать обработчик событий в качестве своего элемента. Программа имеет нарушение прав доступа $C00000005 при выходе из программы.
Все в порядке, если я использую TList Delphi RTL.
Нарушение доступа происходит как для 32-битной, так и для 64-битной сборки. Когда это происходит, кажется, что он останавливается в следующих строках Spring4D:
procedure TCollectionBase<T>.Changed(const item: T; action:
TCollectionChangedAction);
begin
if fOnChanged.CanInvoke then
fOnChanged.Invoke(Self, item, action);
end;
Следующий пример программы может воспроизвести нарушение доступа, используя RAD Studio Tokyo 10.2.3, в Windows.
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.
1 ответ
Это дефект компилятора, который влияет на дженерики. Время жизни TMyClass
экземпляр на самом деле не актуален. Код, который не может обработать компилятор, находится в TList<T>.DeleteRangeInternal
в Spring.Collections.Lists
, Этот код:
if doClear then
Changed(Default(T), caReseted);
Помни что T
это указатель на метод, то есть тип с двумя указателями. Как таковой он больше, чем регистр. Компилятор поворачивает вызов Changed
в это:
Spring.Collections.Lists.pas.641: Изменено (по умолчанию (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 байта, а затем передает эти четыре байта 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 вызов dword ptr [ecx+$3c]
Первые две строки кода asm читают указатель на метод из стека. Таким образом, ABI для параметров указателя метода состоит в том, что они передаются в стек. Это задокументировано следующим образом:
Указатель на метод передается в стек как два 32-битных указателя. Указатель экземпляра помещается перед указателем метода, так что указатель метода занимает самый низкий адрес.
Вернуться к коду, который вызвал эту функцию. Он передал аргумент в регистр. Это несоответствие является причиной исключения, которое на самом деле происходит гораздо позже. Но здесь все идет на юг.
Давайте посмотрим на обходной путь. Мы меняем код в TList<T>.DeleteRangeInternal
быть таким:
var
defaultItem: T;
....
if doClear then
begin
defaultItem := Default(T);
Changed(defaultItem, caReseted);
end;
Теперь сгенерированный код выглядит так:
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: изменено (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 вызов dword ptr [ecx+$74]
Обратите внимание, что этот временной код генерируется, чтобы обнулить оба указателя в указателе метода, а затем передать их через стек. Этот код вызова соответствует коду вызываемого абонента. И все хорошо.
Я отправлю этот обходной путь в мой личный репозиторий Spring4D, и Стефан объединит его с веткой исправлений 1.2.2 в главном репо.
Я отправил отчет об ошибке: RSP-20683.