如何检查对程序的引用是否为零?
How to check if a reference to procedure is nil?
在以下示例代码中,对 AssertTestObj()
的调用导致访问冲突。
Project InvokeTest2.exe raised exception class $C0000005 with message
'access violation at 0x00000000: read of address 0x00000000'.
调试时我可以看到 TSafeCall<T>.Invoke()
中的 Assigned(NotifyProc)
测试没有按预期工作 - 因此 Invoke()
尝试执行 NotifyProc
即 nil
从而导致访问冲突。
知道失败的原因以及解决方法吗?
program InvokeTest2;
{$APPTYPE CONSOLE}
uses
System.SysUtils;
type
TSafeCall<T> = class
public
type
TNotifyProc = reference to procedure (Item: T);
class procedure Invoke(NotifyProc: TNotifyProc; Item: T); overload;
end;
TOnObj = procedure (Value: String) of object;
{ TSafeCall<T> }
class procedure TSafeCall<T>.Invoke(NotifyProc: TNotifyProc; Item: T);
begin
if Assigned(NotifyProc) then
NotifyProc(Item);
end;
procedure AssertTestObj(OnExceptionObj_: TOnObj; Value_: String);
begin
TSafeCall<String>.Invoke(OnExceptionObj_, Value_);
end;
begin
try
TSafeCall<String>.Invoke(nil, 'works as expected');
AssertTestObj(nil, 'this causes an access violation!');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
这是一个编译器错误。这是我的简化复制:
{$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
的测试是正确的。您期望它会按您的预期运行是正确的。问题是当编译器生成代码从CallInvokeOnObject
调用Invoke
时,它需要将对象的方法包装在一个引用过程接口中。为了正确地做到这一点,它需要测试是否分配了对象的方法。如果不是,则不应创建包装器接口,并且应传递 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 [=21=]
004064DD 53 push ebx
004064DE 33C0 xor eax,eax
004064E0 55 push ebp
004064E1 683B654000 push [=21=]40653b
004064E6 64FF30 push dword ptr fs:[eax]
004064E9 648920 mov fs:[eax],esp
004064EC B201 mov dl,
004064EE A1F4634000 mov eax,[[=21=]4063f4]
004064F3 E8DCDAFFFF call TObject.Create
004064F8 8BD8 mov ebx,eax
004064FA 8D45FC lea eax,[ebp-]
004064FD 8BD3 mov edx,ebx
004064FF 85D2 test edx,edx
00406501 7403 jz [=21=]406506
00406503 83EAF8 sub edx,-
00406506 E881F2FFFF call @IntfCopy
0040650B 8B4508 mov eax,[ebp+]
0040650E 894310 mov [ebx+],eax
00406511 8B450C mov eax,[ebp+[=21=]c]
00406514 894314 mov [ebx+],eax
Project18.dpr.17: Invoke(OnObject);
00406517 8BC3 mov eax,ebx
00406519 85C0 test eax,eax
0040651B 7403 jz [=21=]406520
0040651D 83E8E8 sub eax,-
00406520 E8DFFDFFFF call Invoke
对 TObject.Create
的调用将对象的方法包装在引用过程接口中。请注意,该接口是无条件创建的,然后传递给 Invoke
。
您无法从内部解决此问题 Invoke
。当代码到达那里时为时已晚。您无法检测到该方法未分配。这应该作为错误报告给 Embarcadero。
您唯一可行的解决方法是在 CallInvokeOnObject
.
中添加额外的指定检查
在以下示例代码中,对 AssertTestObj()
的调用导致访问冲突。
Project InvokeTest2.exe raised exception class $C0000005 with message 'access violation at 0x00000000: read of address 0x00000000'.
调试时我可以看到 TSafeCall<T>.Invoke()
中的 Assigned(NotifyProc)
测试没有按预期工作 - 因此 Invoke()
尝试执行 NotifyProc
即 nil
从而导致访问冲突。
知道失败的原因以及解决方法吗?
program InvokeTest2;
{$APPTYPE CONSOLE}
uses
System.SysUtils;
type
TSafeCall<T> = class
public
type
TNotifyProc = reference to procedure (Item: T);
class procedure Invoke(NotifyProc: TNotifyProc; Item: T); overload;
end;
TOnObj = procedure (Value: String) of object;
{ TSafeCall<T> }
class procedure TSafeCall<T>.Invoke(NotifyProc: TNotifyProc; Item: T);
begin
if Assigned(NotifyProc) then
NotifyProc(Item);
end;
procedure AssertTestObj(OnExceptionObj_: TOnObj; Value_: String);
begin
TSafeCall<String>.Invoke(OnExceptionObj_, Value_);
end;
begin
try
TSafeCall<String>.Invoke(nil, 'works as expected');
AssertTestObj(nil, 'this causes an access violation!');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
这是一个编译器错误。这是我的简化复制:
{$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
的测试是正确的。您期望它会按您的预期运行是正确的。问题是当编译器生成代码从CallInvokeOnObject
调用Invoke
时,它需要将对象的方法包装在一个引用过程接口中。为了正确地做到这一点,它需要测试是否分配了对象的方法。如果不是,则不应创建包装器接口,并且应传递 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 [=21=] 004064DD 53 push ebx 004064DE 33C0 xor eax,eax 004064E0 55 push ebp 004064E1 683B654000 push [=21=]40653b 004064E6 64FF30 push dword ptr fs:[eax] 004064E9 648920 mov fs:[eax],esp 004064EC B201 mov dl, 004064EE A1F4634000 mov eax,[[=21=]4063f4] 004064F3 E8DCDAFFFF call TObject.Create 004064F8 8BD8 mov ebx,eax 004064FA 8D45FC lea eax,[ebp-] 004064FD 8BD3 mov edx,ebx 004064FF 85D2 test edx,edx 00406501 7403 jz [=21=]406506 00406503 83EAF8 sub edx,- 00406506 E881F2FFFF call @IntfCopy 0040650B 8B4508 mov eax,[ebp+] 0040650E 894310 mov [ebx+],eax 00406511 8B450C mov eax,[ebp+[=21=]c] 00406514 894314 mov [ebx+],eax Project18.dpr.17: Invoke(OnObject); 00406517 8BC3 mov eax,ebx 00406519 85C0 test eax,eax 0040651B 7403 jz [=21=]406520 0040651D 83E8E8 sub eax,- 00406520 E8DFFDFFFF call Invoke
对 TObject.Create
的调用将对象的方法包装在引用过程接口中。请注意,该接口是无条件创建的,然后传递给 Invoke
。
您无法从内部解决此问题 Invoke
。当代码到达那里时为时已晚。您无法检测到该方法未分配。这应该作为错误报告给 Embarcadero。
您唯一可行的解决方法是在 CallInvokeOnObject
.