Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Override Delphi function System.Round

I just discovered that a software I have to reimplement uses extensivelly System.Round(). The problem is that this function uses "Bankers rounding" and the behaviour can not be changed like in Math.RoundTo() (rmDown,rmUp,rmNearest,rmTruncate).

I have to change the behaviour to "normal rounding" (12.5 -> 13 NOT 12.5 -> 12)... So I would like to override System.Round() globally. I want to do this, because Round() is used so many times and I do not want to change them all manually.

How is this possible?

like image 589
Michael Avatar asked Jun 07 '13 08:06

Michael


1 Answers

WARNING: Although the answer below addresses the question that was asked, I would recommend that nobody ever uses it. If you want to perform rounding differently from Round then write and call a dedicated function.


You can use a runtime code hook to change the implementation of Round.

The wrinkle is that it's a little tricky to get hold of the address of the Round function though because it is an intrinsic. You also have to be careful to follow the calling convention used. The input value is passed in the x87 stack register ST(0) and the return value is a 64 bit integer in EDX:EAX.

Here's how to do it.

procedure PatchCode(Address: Pointer; const NewCode; Size: Integer);
var
  OldProtect: DWORD;
begin
  if VirtualProtect(Address, Size, PAGE_EXECUTE_READWRITE, OldProtect) then 
  begin
    Move(NewCode, Address^, Size);
    FlushInstructionCache(GetCurrentProcess, Address, Size);
    VirtualProtect(Address, Size, OldProtect, @OldProtect);
  end;
end;

type
  PInstruction = ^TInstruction;
  TInstruction = packed record
    Opcode: Byte;
    Offset: Integer;
  end;

procedure RedirectProcedure(OldAddress, NewAddress: Pointer);
var
  NewCode: TInstruction;
begin
  NewCode.Opcode := $E9;//jump relative
  NewCode.Offset := 
    NativeInt(NewAddress)-NativeInt(OldAddress)-SizeOf(NewCode);
  PatchCode(OldAddress, NewCode, SizeOf(NewCode));
end;

function System_Round: Pointer;
asm
  MOV     EAX, offset System.@Round
end;

procedure _ROUND;
asm
        { ->    FST(0)  Extended argument       }
        { <-    EDX:EAX Result                  }

        // your implementation goes here
end;

initialization
  RedirectProcedure(System_Round, @_ROUND);

If you'd rather implement your version in Pascal than asm then you need to adapt the non-standard calling convention of _ROUND to the standard Delphi calling convention. Like this:

function MyRound(x: Extended): Int64;
begin
  // your implementation goes here
end;

procedure _ROUND;
var
  x: Extended;
asm
        { ->    FST(0)  Extended argument       }
        { <-    EDX:EAX Result                  }

        FSTP    TBYTE PTR [x]
        CALL    MyRound
end;

Note that I have assumed here that your program is targeting 32 bit. If you need to target 64 bit then the principles are much the same, but the details obviously differ.

like image 173
David Heffernan Avatar answered Oct 06 '22 00:10

David Heffernan