はい、他の (親) サブルーチンにネストされたサブルーチンに、外界からアクセスできます。少しトリッキーですが。このハウツーをウェブで見つけました。
ネストされたルーチンを手続き型パラメーターとして渡す方法 (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/アセンブラー コードをうまく処理しません。