如何修补 Delphi 类的私有方法?

问题描述 投票:0回答:3

我已阅读这些问题和答案

如何更改外部声明函数的实现(绕行)

在delphi中修补例程调用

但我无法弄清楚如何修补位于另一个单元中的类的私有方法

检查此示例,我想修补

Bar
程序。

Unit ThidParty;
Interface
   Type
      TFoo =Class
        private
           procedure Bar;
       end;

我觉得关键是想办法获取私有方法的地址。

那么,我如何修补 Delphi 类的私有方法?

delphi delphi-xe2
3个回答
23
投票

下面概述的解决方案适用于 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 现已在柏林修复。你运气不好。


0
投票

只需使用类拦截重新定义方法,它就可以工作到Rad 10.4


0
投票

免责声明:我借用了@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.
© www.soinside.com 2019 - 2024. All rights reserved.