我刚刚发现我必须重新实现的软件广泛使用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位为目标,则原则大致相同,但细节明显不同.