This is a compiler bug. Here's my simplified reproduction:
{$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.
You might wonder why I simplified. Your code was a superb reproduction of the problem. However, I wanted to make it absolutely as simple as possible so that I really could be sure that the problem was what I believe it to be. So I removed the generics and the classes.
Now, the test using Assigned
is correct. You are right to expect that it will behave as you intend. The problem is that when the compiler generates code to call Invoke
from CallInvokeOnObject
, it needs to wrap the method of object in a reference procedure interface. In order to do this correctly it would need to test whether or not the method of object is assigned. If not then no wrapper interface should be created and Invoke
should be passed nil
.
The compiler fails to do that. It unconditionally wraps the method of object in a reference procedure interface. You can see this in the code emitted for 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
That call to TObject.Create
is what wraps the method of object in a reference procedure interface. Note that the interface is created unconditionally and then passed to Invoke
.
There's no way for you work around this from inside Invoke
. By the time the code reaches there it's too late. You cannot detect that the method is not assigned. This should be reported to Embarcadero as a bug.
Your only viable workaround is to add an extra assigned check in CallInvokeOnObject
.