Reputation: 19
Some years ago I've ported an old unix application to Delphi 5. For linked lists iterations it used local procedures passed by address to a global 'iterator' function.
Below is a simplified example:
type TPerformProc = procedure;
procedure Perform(proc:TPerformProc);
begin
proc;
end;
procedure Test;
var loc_var:integer;
procedure loc_proc;
begin
loc_var:=loc_var+10;
end;
begin
loc_var:=0;
writeln('loc var: ',loc_var);
Perform(addr(loc_proc));
writeln('loc var: ',loc_var);
writeln('-----');
end;
The example procedure crashes in Delphi, but it worked on unix just fine.
With some help, I've been able to get it working like this:
type TPerformProc = procedure;
var loc_bp:integer;
procedure Perform(proc:TPerformProc);
begin
asm
push loc_bp
end;
proc;
asm
pop eax
end;
end;
procedure Test;
var loc_var:integer;
procedure loc_proc;
begin
loc_var:=loc_var+10;
end;
begin
loc_var:=0;
writeln('loc var: ',loc_var);
asm
mov loc_bp,ebp
end;
Perform(addr(loc_proc));
writeln('loc var: ',loc_var);
writeln('-----');
end;
To solve the problem, I store a reference to the stack frame of the local procedure and then I call the local procedure.
It's clear to me, that the solution above is not a proper fix, but rather a hack and I understand that a new Delphi version might handle the local procedures in a different way and break the hack. Fortunately enough, this part of Delphi stayed the same and the code works OK even with latest Delhi.
However, I want to compile the application as 64-bit one and the hack is no longer working. So far I was not able to find a similar solution, but for 64-bit. Is here someone who can help here ?
Thank you.
Upvotes: 0
Views: 1106
Reputation: 19
My edit in the original post was rolled back with the request to post it as answer, so here we go.
After further fiddling, following seems to work properly for 32 and 64-bit platform:
type TPerformProc = procedure;
var my_bp:NativeInt;
procedure SimpleFixPerform(proc:TPerformProc);
asm
{$ifdef WIN64}
mov rax,proc
push rbp
mov rbp,my_bp
mov rcx,my_bp
call rax
pop rbp
{$else}
push my_bp
call proc
pop eax
{$endif}
end;
procedure SetupBP;
asm
{$ifdef WIN64}
mov my_bp,rbp
{$else}
mov my_bp,ebp
{$endif}
end;
procedure SimpleFixTest;
var loc_var:integer;
procedure loc_proc;
begin
loc_var:=loc_var+10;
end;
begin
loc_var:=0;
loc_proc;
writeln('SimpleFix var: ',loc_var);
SetupBP;
SimpleFixPerform(@loc_proc);
writeln('SimpleFix var: ',loc_var);
writeln('-----');
end;
My assembler skills are bit rusted, so if you see a gotcha in code code, please comment
Upvotes: 0
Reputation: 34899
A variation of @J...'s answer, also using anonymous methods.
program Project163;
{$APPTYPE CONSOLE}
uses
SysUtils;
procedure Perform(proc:TProc);
begin
WriteLn('doing something else important...');
proc;
end;
procedure Test;
var
loc_var:integer;
function _LocalProc : TProc;
begin
Result :=
procedure
begin
loc_var := loc_var+10;
end;
end;
begin
loc_var:=0;
writeln('loc var: ',loc_var);
Perform(_LocalProc());
writeln('loc var: ',loc_var);
writeln('-----');
end;
begin
Test; ReadLn;
end.
The _LocalProc
is turned into a function, returning an anonymous method, identical to the original local procedure.
Note the extra paranthesis in the call Perform(_LocalProc())
, to make the compiler understand to pass the resulting anonymous method as parameter.
Upvotes: 1
Reputation: 31393
The cleanest solution here is to use anonymous methods. There's no way around this without a bit of refactoring, but you could do it reasonably painlessly like this:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
procedure Perform(proc:TProc);
begin
WriteLn('doing something else important...');
proc;
end;
procedure Test;
var
loc_var:integer;
procedure PerformExt;
begin
Perform(procedure
begin
loc_var := loc_var+10;
end);
end;
begin
loc_var:=0;
writeln('loc var: ',loc_var);
PerformExt;
writeln('loc var: ',loc_var);
writeln('-----');
end;
begin
Test; ReadLn;
end.
This produces output :
loc var: 0
doing something else important...
loc var: 10
-----
Note that the definition for Perform
has to change to accept a TProc
rather than your custom procedure
alias.
Upvotes: 1