How to pass nested routine as a procedural parameter (32 bit ) -------------------------------------------------------------- Delphi normally does not support passing nested routines as procedural parameters: { This code does not compile:} procedure testpass(p: tprocedure); begin p; end; procedure calltestpass; procedure inner; begin showmessage('hello'); end; begin testpass(inner); end; The obvious workaround is to pass procedure address and typecast it within 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; There is, however, a pitfall in the above example - if the "inner" routine references any variable that was pushed onto the stack before the "inner" procedure was called from testpass (calltestpass parameters - if there were any, or local variables in calltestpass - if there were any), your system most probably crashes: { 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; The reason is, in simple words, that the stack frame arrangement was "broken" by the call to testpass routine and "inner" procedure incorrectly calculates parameters and local variables location (do not blame Delphi, please). The workaround is to set up the correct stack context before "inner" is called from within "testpass". { 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; Please note the optimization is switched OFF for testpass routine - optimization generally does not handle mixed OP/assembler code very well. Now it is easy to write, as an example, a generic dataset iteration routine: {$O+} procedure DoFortable(Tbl: tDataset; Action: pointer); { Action must be a nested procedure with the declaration procedure(atable: tDataset); and with register calling convention (Default in 32bit Delphi) } var callersBP: longint; wasopen : boolean; BkMrk : tBookmark; begin asm { get caller's base pointer value at the very beginning } push dword ptr [ebp] pop callersBP end; wasopen := tbl.active; Tbl.Disablecontrols; if not wasopen then Tbl.open else BkMrk := Tbl.GetBookmark; Try Tbl.First; While not tbl.EOF do begin asm push CallersBP mov eax,tbl { Action has register calling convention : EAX <- Tbl } Call Action Pop CallersBP end; Tbl.next; End; Finally if not wasopen then begin Tbl.close; end else begin Tbl.GotoBookmark(BkMrk); Tbl.FreeBookmark(BkMrk); end; Tbl.Enablecontrols; end; end; {$O-} {Example call} function tDataModule1.CountSalaryHigherThan(Limit: currency): longint; var count: longint; Procedure CountIt(Tbl: tdataset); begin if Tbl.FieldByname('Salary').AsCurrency > Limit then inc(count); end; begin count := 0; DoFortable(tableEmployee,@CountIt); Result := count; end;