Ответ 1
Это ошибка компилятора. Здесь мое упрощенное воспроизведение:
{$APPTYPE CONSOLE}
type
TProc = reference to procedure;
TOnObject = procedure of object;
procedure Invoke(Proc: TProc);
begin
if Assigned(Proc) then
Proc();
end;
procedure CallInvokeOnObject(OnObject: TOnObject);
begin
Invoke(OnObject);
end;
begin
Invoke(nil); // succeeds
CallInvokeOnObject(nil); // results in AV
end.
Вы можете удивиться, почему я упростил. Ваш код был превосходным воспроизведением проблемы. Тем не менее, я хотел сделать это максимально простым, чтобы я действительно мог быть уверен, что проблема в том, что я считаю. Поэтому я удалил дженерики и классы.
Теперь проверка с использованием Assigned
верна. Вы правы ожидать, что он будет вести себя так, как вы намереваетесь. Проблема заключается в том, что когда компилятор генерирует код для вызова Invoke
из CallInvokeOnObject
, ему необходимо обернуть метод объекта в интерфейс ссылочной процедуры. Чтобы сделать это правильно, необходимо проверить, назначен ли метод объекта. Если нет, тогда не должен быть создан интерфейс оболочки и Invoke
должен быть передан nil
.
Компилятор не может этого сделать. Он безоговорочно обертывает метод объекта в интерфейсе эталонной процедуры. Вы можете видеть это в коде, испускаемом для CallInvokeOnObject
.
Project1.dpr.16: begin // this is the beginning of CallInvokeOnObject 004064D8 55 push ebp 004064D9 8BEC mov ebp,esp 004064DB 6A00 push $00 004064DD 53 push ebx 004064DE 33C0 xor eax,eax 004064E0 55 push ebp 004064E1 683B654000 push $0040653b 004064E6 64FF30 push dword ptr fs:[eax] 004064E9 648920 mov fs:[eax],esp 004064EC B201 mov dl,$01 004064EE A1F4634000 mov eax,[$004063f4] 004064F3 E8DCDAFFFF call TObject.Create 004064F8 8BD8 mov ebx,eax 004064FA 8D45FC lea eax,[ebp-$04] 004064FD 8BD3 mov edx,ebx 004064FF 85D2 test edx,edx 00406501 7403 jz $00406506 00406503 83EAF8 sub edx,-$08 00406506 E881F2FFFF call @IntfCopy 0040650B 8B4508 mov eax,[ebp+$08] 0040650E 894310 mov [ebx+$10],eax 00406511 8B450C mov eax,[ebp+$0c] 00406514 894314 mov [ebx+$14],eax Project18.dpr.17: Invoke(OnObject); 00406517 8BC3 mov eax,ebx 00406519 85C0 test eax,eax 0040651B 7403 jz $00406520 0040651D 83E8E8 sub eax,-$18 00406520 E8DFFDFFFF call Invoke
Этот вызов TObject.Create
- это то, что обертывает метод объекта в интерфейсе эталонной процедуры. Обратите внимание, что интерфейс создается безоговорочно, а затем передается в Invoke
.
Нет никакого способа обойти это изнутри Invoke
. К тому времени, когда код дойдет до него, это слишком поздно. Вы не можете обнаружить, что метод не назначен. Это должно быть сообщено Embarcadero как ошибка.
Единственным возможным решением является добавление дополнительной назначенной проверки в CallInvokeOnObject
.