是的,您可以从外部世界访问嵌套在其他(父)子程序中的子程序。虽然有点棘手。我在网上找到了这个howto。
如何将嵌套例程作为过程参数传递(32 位)
Delphi 通常不支持将嵌套例程作为过程参数传递:
// This code does not compile:
procedure testpass(p: tprocedure);
begin
p;
end;
procedure calltestpass;
procedure inner;
begin
showmessage('hello');
end;
begin
testpass(inner);
end;
显而易见的解决方法是在 testpass 中传递过程地址并对其进行类型转换:
// This code compiles and runs OK
procedure testpass(p: pointer);
begin
tProcedure(p);
end;
procedure calltestpass;
procedure inner;
begin
showmessage('hello');
end;
begin
testpass(@inner);
end;
然而,在上面的例子中有一个陷阱——如果“内部”例程引用了在从 testpass 调用“内部”过程之前被压入堆栈的任何变量(calltestpass 参数——如果有任何变量,或者calltestpass - 如果有的话),你的系统很可能会崩溃:
// This code compiles OK but generates runtime exception (could even be
// EMachineHangs :-) )
procedure testpass(p: pointer);
begin
tProcedure(p);
end;
procedure calltestpass;
var msg: string;
procedure inner;
begin
msg := 'hello';
showmessage(msg);
end;
begin
testpass(@inner);
end;
简单来说,原因是堆栈帧排列被调用 testpass 例程“破坏”了,并且“内部”过程错误地计算了参数和局部变量的位置(请不要责怪 Delphi)。解决方法是在“testpass”中调用“inner”之前设置正确的堆栈上下文。
// This code compiles and runs OK
{$O-}
procedure testpass(p: pointer);
var callersBP: longint;
begin
asm // get caller's base pointer value at the very beginning
push dword ptr [ebp]
pop callersBP
end;
// here we can have some other OP code
asm // pushes caller's base pointer value onto stack and calls tProcedure(p)
push CallersBP
Call p
Pop CallersBP
end;
// here we can have some other OP code
end;
{$O+}
procedure calltestpass;
var msg: string;
procedure inner;
begin
msg := 'hello';
showmessage(msg);
end;
begin
testpass(@inner);
end;
请注意,对于 testpass 例程,优化已关闭 - 优化通常不能很好地处理混合 OP/汇编代码。