这篇文章介绍一些
Delphi
穿透
OOP
约束的技巧。
访问保护的
(protected)
变量
如果是某个类的保护变量,可以在任何地方,通过如下方法访问:
type
TSomeClassAccess = class (TSomeClass);
begin
TSomeClassAccess(Object1).protected_Bool := False;
TSomeClassAccess(Object1).protected_Int := 0 ;
...
end ;
TSomeClassAccess = class (TSomeClass);
begin
TSomeClassAccess(Object1).protected_Bool := False;
TSomeClassAccess(Object1).protected_Int := 0 ;
...
end ;
访问私有的
(private)
变量
如果是某个类的私有变量,我们需要计算该变量在
VMT
中的偏移量。同时由于类声明可能在不同的
Delphi
版本中也有所不同,所以最好事先先检查一下源码。例:访问
TMenuItem
的第二个内部成员变量
FHandle
type
THackMenuItem = class (TComponent)
protected //<-- change to protected
FxxxxCaption: AnsiString;
FHandle: HMENU; //<-- the property you want to access
begin
THackMenuItem(AMenuItem).FHandle := 0;
...
end ;
THackMenuItem = class (TComponent)
protected //<-- change to protected
FxxxxCaption: AnsiString;
FHandle: HMENU; //<-- the property you want to access
begin
THackMenuItem(AMenuItem).FHandle := 0;
...
end ;
访问部分私有的
(private)
函数
访问私有函数要相对困难许多。据我所知只有
定义时声明为
virtual
、
override
、
dynamic
、
message
的
私有函数才可以被访问或替换。其实现
原理和访问私有变量相似:先计算该函数在
VMT/DMT
中的偏移量
,
然后把该内存地址替换成新函数的内存地址。具体做法可以参考
TntControls
中
TntSystem.pas
安装系统补丁
,
或者是
Fastcode
控件包。
友情提醒: (1) 通常私有函数中会涉及到一些访问其它的私有函数 / 变量。往往为了访问一个私有函数,还需要修改更多个私有函数 / 变量。相对比较复杂,也不很可靠。 (2) 内存地址修改不当会引发于一些软件的冲突,如 AQTime 。
友情提醒: (1) 通常私有函数中会涉及到一些访问其它的私有函数 / 变量。往往为了访问一个私有函数,还需要修改更多个私有函数 / 变量。相对比较复杂,也不很可靠。 (2) 内存地址修改不当会引发于一些软件的冲突,如 AQTime 。
注:如果变量定义时未设置关键字
strict
,同一个单元里面的所有类的内部变量
/
函数可以直接相互访问。这个是
Delphi
开的一个不大不小的后门。
增加私有的
(private)
函数
(a)
我们真的需要吗?有时候要!比如我在写一个
Dunit
单元测试的时候,需要接受
WM_COPYDATA
消息。但是这个消息是发送给
GUITestRunner
的,我必须打入
GUITestRunner
才可以得到相应消息。实现方法和访问私有函数类似,我们计算
VMT/DMT
的长度,在尾端增加一个新的函数指针。我在网上找到一篇文章说的比较详细,请
点击这里查看。这个方法有个限制:这个修改不是编译期完成的,你必须在类实例化之后,才进行修改。如果这个类被频繁使用,这个做法显然有些麻烦。如果能把这段修改代码放如类的构析函数中,问题就迎刃而解了。我在假想:我们是否可以使用介绍3中引入的方法,实现这个……
增加私有的
(private)
函数
(b)
还有一种更干净的修改方法,我们定义一个同名的类来欺骗编译器。例:我们创建一个新的单元,名为
GUITestRunnerPatch.pas
type
THackGUITestRunner = class (GUITestRunner.TGUITestRunner)
private
procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA; //<-- the method you want to append
end;
THackGUITestRunner = class (GUITestRunner.TGUITestRunner)
private
procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA; //<-- the method you want to append
end;
在需要使用
GUITestRunner
的地方
,
加上新的
GUITestRunnerPatch。注意:一定要在其之后,否则编译器不会调用你修改过的这个类,而是调用了原先的那个。
总结:
在进入
Delphi
的内部世界时,我们要尽可能的考虑代码的移植性和通用性,要以少量修改换回最佳效果。如果上面介绍的修修补补不能解决你的问题,当然你也可以直接修改
Delphi
的控件源码。修改之后,勾选编译选项里面的 „
Use Debug DCUs
“,并编译程序,再将编译得到的
dcu
文件保存到编译目录下面。我一般创建
2
个目录:
PatchedVCLs
和
PreCompiled
放修改过的源码,和编译之后的版本。然后把这个
2
个目录定义到环境变量里面,这样只要在每个项目的路径设置中添加这些变量,就可以使用改动过的代码了。如果本文不正确的地方,欢迎拍砖。