我刚刚发现我必须重新实现的软件广泛使用System.Round().问题是这个函数使用“Bankers rounding”,并且不能像Math.RoundTo()(rmDown,rmUp,rmNearest,rmTruncate)那样改变行为.
我必须将行为更改为“正常舍入”(12.5 – > 13 NOT 12.5 – > 12)…所以我想全局覆盖System.Round().我想这样做,因为Round()被使用了很多次,我不想手动更改它们.
这怎么可能?
解决方法
警告:虽然下面的答案解决了所提出的问题,但我建议没有人使用它.如果要执行与Round不同的舍入,则编写并调用专用函数.
您可以使用运行时代码钩子来更改Round的实现.
皱纹是得到Round函数的地址有点棘手,因为它是一个内在的.您还必须小心遵循使用的调用约定.输入值在x87堆栈寄存器ST(0)中传递,返回值在EDX:EAX中为64位整数.
这是怎么做的.
- 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,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);
如果您宁愿在Pascal中实现您的版本而不是asm,那么您需要使_ROUND的非标准调用约定适应标准的Delphi调用约定.像这样:
- 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;
请注意,我假设您的程序的目标是32位.如果您需要以64位为目标,则原则大致相同,但细节明显不同.