此文是参考 原文链接 和群友及作者自己曾经用过的方法简单总结,感谢群内的朋友和原文作者。另外,此文中所谓的访问另一个类的私有成员,另一个类并不在同一单元,在同一单元内的私有成员 Delphi 也是可以直接访问的,不需要绕圈子。
1、最易用的方法:class helper + with self
创建一个对应类的 class helper,然后在其中用 with self do 来访问其私有成员。不过这个不知道是不是算 bug,如果算的话,官方可能会在以后修正,如果不算,大家就这么用吧。
type TMyHelperForClass=class helper for TMyClass ... end; ...TMyHelperForClass.xxxx... begin ... with Self do begin //在这里就可以访问 TMyClass 的私有成员了 end end;
2、使用 TMethod 方法访问私有函数
这个本质上来说,就是设置 TMethod.Code 和 Data 成员的地址,其中 Code 对应私有函数地址,然后 Data 指向 Self,然后声明下私有方法的类型,转换下就好。
type TPrivateMethodType=... of object; ... var AMethod:TMethod; AInvoke:TPrivateMethodType absolute AMethod; begin AMethod.Code:=@TMyClass.PrivateMethodName; AMethod.Data:=Self; AInvoke(...); end;
当然,这个仅限于私有函数,不包含成员。
3、最保守的用法:RTTI
利用新版 Delphi 的 Rtti 支持,可以获取私有成员的地址,并进行调用。效率不如前面几种方法,但相对来说,更可控一些。直接搬过来 qdac_fmx_vkhelper 的代码,给大家参考:
procedure UpdateAndroidKeyboardServiceState; var ASvc: IFMXVirtualKeyboardService; AContext: TRttiContext; AType: TRttiType; AField: TRttiField; AInst: TVirtualKeyboardAndroid; begin if not Assigned(FVKState) then begin if (not Assigned(Screen.FocusControl)) and TPlatformServices.Current.SupportsPlatformService (IFMXVirtualKeyboardService, ASvc) then begin AInst := ASvc as TVirtualKeyboardAndroid; AContext := TRttiContext.Create; AType := AContext.GetType(TVirtualKeyboardAndroid); AField := AType.GetField('FState'); if AField.GetValue(AInst).AsOrdinal <> 0 then begin FVKState := PByte(AInst); Inc(FVKState, AField.Offset); end; end; end; if Assigned(FVKState) and (FVKState^ <> 0) then FVKState^ := 0; end;
4、有风险的方法:套娃
套娃这种办法,在以前用过,因为要求成员的布局和原始的函数一样,所以我觉得万一原始类升级,这块都得跟着改,所以不太推荐这么用。这种一般是用来访问私有成员变量时比较方便,如果是私有函数,则由于代码地址不一样,不能直接用,但可以通过计算成员函数地址偏移的方式,来完成调用。总体来说,套娃虽好,慎用为佳。
5、最难用的方法:汇编
好吧,这觉得这个已经超过大部分普通程序员的能力了,asm JMP 指令跳转到目标地址去执行就可以调用函数,实际上直接asm call 应该也是一样的。如果函数没有参数还好,有的话,你得研究下如何入栈/出栈/栈平平衡的问题,对于大部分还是算了吧。原文有例子,不过我觉得还是“就这样算了吧,就这样忘子吧,该放就放,再想也没有用,傻傻等待……”,有唱出来的说明是同龄人,老了~~~~
【其它】
访问保护成员的办法就简单多了,直接用class helper 或者是实现一个子类(这个子类啥也不需要做,只是父类的一个转换器),然后就可以了。