我已阅读这些问题和答案
但我无法弄清楚如何修补位于另一个单元中的类的私有方法。
检查此示例,我想修补
Bar
程序。
Unit ThidParty;
Interface
Type
TFoo =Class
private
procedure Bar;
end;
我觉得关键是想办法获取私有方法的地址。
那么,我如何修补 Delphi 类的私有方法?
下面概述的解决方案适用于 Delphi Seattle 及以上版本。 您可以使用 class helper 来破解该类:
单元1
type
TTest = class
private
procedure Foo;
end;
单元2
type
TMyTestHelper = class helper for TTest
function GetFooAddress: Pointer;
end;
function TMyTestHelper.GetFooAddress: Pointer;
var
MethodPtr: procedure of object;
begin
MethodPtr := Self.Foo;
Result := TMethod(MethodPtr).Code;
end;
function FooAddress: Pointer;
begin
Result := TTest(nil).GetFooAddress;//don't need to instantiate an object
end;
将返回值从
FooAddress
传递到您的修补函数之一,您就成功了。
但是,从 Delphi 10.1 Berlin 开始,这不再有效!类助手不能再访问严格受保护、严格私有或私有成员。这个“功能”实际上是一个编译器错误,Embarcadero 现已在柏林修复。你运气不好。
只需使用类拦截重新定义方法,它就可以工作到Rad 10.4
免责声明:我借用了@david-heffernan的答案,@lu-rd对该答案的评论,以及这个this page,其中写了
with
技巧。
下面的示例适用于 Delphi 12.1。
背景(或长篇大论;直接跳到答案):将项目从 Delphi 7 迁移到 Delphi 12.1 后,发现使用
TDateTimePicker.Date
的地方,时间部分包含在结果 TDateTime
中,但是在某些时候(也许甚至在 Delphi 12.1 中,我不知道)这被更改为删除时间部分,仅返回结果中的日期TDateTime
。需要紧急修复。我们认为,考虑到时间限制,将可能数百个 Date
实例更改为 DateTime
太容易出错。补充一下,我对 Delphi 的一大烦恼是,既然它有了 LSP,为什么他们不优先考虑准确查找给定符号的所有引用的能力呢?现有的“重构”菜单已被弃用和损坏,而且很糟糕的是,我必须反复求助于类帮助程序和黑客(例如使用 Detours 库的蹦床)以避免进行容易出错的大规模更改 - 我可以在 C# 中通过几次点击和击键来完成这些更改(很多年前就可以做到)。我想我可以将 Vcl.ComCtrls.pas
的副本添加到项目中,将现有 Date
属性重命名为 DateBlahBlahBlah
之类的名称,编写一个执行编译的自动化脚本,等待错误,将按键发送到编辑器或进行编辑找到 DateBlahBlahBlah
的文件并将其更改为 DateTime
,但我不打算这样做(还:D)。
我寻求的修复涉及覆盖以下行为:
function GetDate: TDate;
位于:
TCommonCalendar = class(TWinControl)
位于:
Vcl.ComCtrls.pas
我已经在一个单独的单元中完成了这项工作,该单元只是添加到项目中。下面完整地粘贴了。请注意,我注释掉了
TCommonCalendarHelper.GetGetDateAddress
的另一个版本,它显示了使用 with
获取私有方法地址的另一种方法;我选择了看起来更简单/更短的版本。下面的代码中还包含一些访问 TCommonCalendar.DateTime
protected
属性的帮助。
整个单位:
unit D12DateTimePickerFix;
interface
{ Fix for TDateTimePicker.Date no longer also including the time, and TDateTimePicker.Time no longer including the date.
In Delphi 7, getting TDateTimePicker.Date (TDateTime) included the time portion of TDateTime, as shown below:
function TCommonCalendar.GetDate: TDate;
begin
Result := TDate(FDateTime);
end;
Even though there is a cast, it's only symbolic, since TDate is TDateTime.
In Delphi 12.1 (or earlier versions), they changed it so the time portion is stripped out:
function TCommonCalendar.GetDate: TDate;
begin
Result := TDate(DateOf(FDateTime));
end;
Ths fix involves patching TCommonCalendar.GetDate method, or rather, "hooking" into it so our own method is called instead.
The Detours library is utilised again, which is currently needed for a BDE fix. In the future, a search is needed for
all the instances where TDateTimePicker.Date is used, and replaced with DateTime. Let's wait until Delphi's "Refactoring"
features are not in a "deprecated" state.
Similarly, TDateTimePicker.Time no longer includes the date.
In Delphi 7 the code is:
function TDateTimePicker.GetTime: TTime;
begin
Result := TTime(FDateTime);
end;
In Delphi 12.1, the code is:
function TDateTimePicker.GetTime: TTime;
begin
Result := TTime(TimeOf(FDateTime));
if (Result = 0) and ([csWriting, csDesigning] * ComponentState <> []) then
Result := TTime(FDateTime);
end; }
implementation
uses
ComCtrls,
DDetours //need this library for easy injecting/hooking/trampolining (like with madCodeHook)
;
//helper class is needed to get an address of a private method
type
TCommonCalendarHelper = class helper for TCommonCalendar
function GetGetDateAddress: Pointer;
end;
TDateTimePickerHelper = class helper for TDateTimePicker
function GetGetTimeAddress: Pointer;
end;
{ TCommonCalendarHelper }
function TCommonCalendarHelper.GetGetDateAddress: Pointer;
begin
Result := @TCommonCalendar.GetDate;
end;
//alternative way to access a private method using a "with" trick
{function TCommonCalendarHelper.GetGetDateAddress: Pointer;
var
_GetDateMethod: function: TDate of object;
begin
with Self do _GetDateMethod := GetDate;
Result := TMethod(_GetDateMethod).Code;
end;}
{ TDateTimePickerHelper }
function TDateTimePickerHelper.GetGetTimeAddress: Pointer;
begin
Result := @TDateTimePicker.GetTime;
end;
//the rest of the code below relates to using the Detours library to "hijack" the original GetDate and GetTime methods and use my own methods instead, which demonstrates my use-case in full
var
GetDate_Old: function(const _Self): TDate = nil;
GetTime_Old: function(const _Self): TTime = nil;
type
TCommonCalendarAccess = class(TCommonCalendar); //need this to access the protected DateTime property
function GetDate_New(const _Self): TDate;
begin
//var Self: TCommonCalendarAccess := @_Self; Result := Self.DateTime;
Result := TDate(TCommonCalendarAccess(@_Self).DateTime); //restore Delphi 7 behaviour
end;
function GetTime_New(const _Self): TDate;
begin
Result := TTime(TDateTimePicker(@_Self).DateTime); //restore Delphi 7's behaviour
end;
initialization
//intercept the two methods in Vcl.ComCtrls.pas
@GetDate_Old := InterceptCreate(TCommonCalendar.GetGetDateAddress, @GetDate_New);
@GetTime_Old := InterceptCreate(TDateTimePicker.GetGetTimeAddress, @GetTime_New);
finalization
//undo intercepts
InterceptRemove(@GetDate_Old);
InterceptRemove(@GetTime_Old);
end.