如何在不同的单元访问类的私有方法?(200分)

  • 主题发起人 主题发起人 天地弦
  • 开始时间 开始时间

天地弦

Unregistered / Unconfirmed
GUEST, unregistred user!
主要是如何得到对象私有方法的地址?
unit1

type
TGetClassName = function: string of object;
TBsClass = class(TObject)
private
FField1: string;
function GetClassName: string;
public
constructor Create()
virtual;
procedure DoSomething1;
end;


unit2
...............

type
TInherClass1 = class(TBsClass)
private
FGetClassName: TGetClassName;
public
constructor Create()
override;
procedure DoSomething2;
end;

怎么样可以在TInherClass1类中访问到TBsClass的GetClassName私有方法。
这里只是举个例子,有时候基类并不是我们可以改写的:)
 
私有方法和属性本来就是避免要访问的为的是不破坏类的封装性,楼主干什么还要访问它呢?要是想要访问该方法的话不如就把该方法公开了不就是可以了吗?
 
To Johnny_du
这个问题,我引用aminigoo的回答

张无忌 (2003-05-29 22:04:00)
破坏封装性,没有这个必要![:D]

aimingoo (2003-05-29 22:08:00)
to 张无忌,
-----
封装的结果是:在里面的人想出出不去,在外面的想进进不来。如果你做Delphi
Expert/Wizad,或者利它开发插件、脚本引擎等,就知道有用了。

本文中对这个技术的应用性也做了一些介绍。哈,不要在没有看之前就打一棍子哦。:)
 
我在写一个将界面保存到流和从流还原的分析器,读取类是从TReader继承,写是从TWriter类继承。我没有使用基类中的ReadComponent和WriteComponent,因为我要过滤掉一些不需要写入的属性,这样本来可以改写这两个方法,但是Borland没有将他申明成virtual和dynamic方法,所以改写了也没有用,只有重新写过两个方法。基类中访问到Classes的很多私有成员,私有类,没办法。。。
 
参考李维的《VCL 深入》。
关键是DELPHI FOR VCLFORM没有提供影射机智。但是你如果知道这个父类的话,就可以知道它的内存在那里了。这个也就不在困难。
使用AS函数可以办到。但是可能会有内容的缩小,这时候,需要新建一个,进行内存转化,李维说,AS实际上是对内存(我不记得了),进行的UP(缩小)。这样就可以访问了。
当然地址也能取得到。
时间长了,不记得了。。
有一些关于破坏封装的用法。
不大清楚了。
。。。。
。。。
另外:天地弦, 是不是写DLL+PACKAGE+INTERFACE的那位了不起的大侠呢?
 
如何跨单元、跨类地访问Delphi类的私有域(摘抄,出处已忘)

Delphi约定,一个私有方法不能被其它模块中调用,同时,一个私有属性不能在其它模块中读写。但Delphi也在同一单元中放宽了上述限制,也就是说,你可以在同一单元的一个类中访问其它类的私有域和私有方法。
但是,有没有办法不在同一单元访问它们呢?
似乎Delphi不提供这样的机制,但是,我们的确可以做到!

一、 问题是怎样被提出的?
OOP方法总是试图安全、快速地构造一个类,并在此基础上继承出子类。为了保证子类对父类操作,以及类实例操作的安全性,OOP中引入了类的私有域的概念。也就是通常说的private域。
如前所述,你可以改写一个类的单元,在该单元中实现对类的私有域的访问。但是,没有人是愿意为每一个Delphi单元来改写代码以实现上述要求,更多人总是希望如同访问一个公开域一样直接地访问私有域,而不受限于任何单元。
——这是不安全、不允许的!OOP方法的基础理论会这样告诉你。
我们应当承认这样做的“不安全”,但Funs们不会接受“不允许”。
问题的提出总是在解决另一个问题的过程之中。——编程历来如此!下面,我将结合一个实现快速删除TList的一批连续结点时的实例,来描述问题提出和实现的全部过程。它将提出一种全新而有效地跨单元、跨类地访问Delphi类的私有域的方法。
按照目前Delphi对类的实现机制和方法,这样的一个“后门”还将永远地开下去,Funs们就好好的利用吧。哈哈。
*注:下面的实现过程最早被我发布在Delphi大富翁论坛(www.delphibbs.com)上,但在一个长篇累牍的贴子里,它并没有引起大家的重视,于是我重新整理了本文。至于原文,大家不妨到下面的贴子里去看看(建议你仔细而小心地阅览该贴,否则你会被淹没的……):
http://www.delphibbs.com/delphibbs/dispq.asp?lid=650664

二、 从TList开始分析……
为了写一个更好的性能ISAPI Filter,我需要更快速地从TList中删除部分连续的Item。比如这样的一段代码:
var p : pChar = 'abcdefgh';
procedure TestDelFromTList;
var t1 : TList;
i : integer;
maxI : integer;
begin
t1 := tlist.create;
t1.count := 100000;
for i:=0 to t1.count-1 do t1 := p;
maxI := t1.count-10-1
//最后一个结点
for i:= maxI downto 10 do t1.delete(i);
//正向删除
//for i:=10 to t2.count-10-1 do t2.delete(10);
end;
这段代码是初始化一个100000个结点的List,然后删除其中的第10个到倒数第10个。这段代码是逆向的,这样的删除速度比较快。如果换成正向删除(已经注释掉),则速度就慢得非常多了。
这样的删除是正常的算法。用测效率的程序测试:逆向删除算法的耗时是21.66个毫秒,则正向删除的耗时却能达到58099.02个毫秒。速度慢了2680倍!!!
但这样就很快了么?不是!我认为就算是逆向删除的速度也并不是快的。
分析TList这个类的源码,我们可以看到,它是这样写的(我加入了注释):
procedure TList.Delete(Index: Integer);
var
Temp: Pointer;
begin
if (Index < 0) or (Index >= FCount) then //判定Index值是否超界
Error(@SListIndexError, Index);
Temp := Items[Index]
//取待删除结点
Dec(FCount)
//Count减一
if Index < FCount then //将待删除结点后的Buffer提前
System.Move(FList^[Index + 1], FList^[Index],(FCount - Index) *
SizeOf(Pointer))

if Temp <> nil then //发通告
Notify(Temp, lnDeleted)

end;
由于在TList类是将全部的结点指针存放在FList这个动态数组的指针中,所以只需要将Index+1之后的内存块向前移4个字节,即SizeOf(Pointer),即可实现Index结点的删除。
但是,如果使用这样来删除成批连续的(N个)结点,则要实现N次system.move()操作,操作的内存块的大小决定了system.move()操作的耗时,而Index值越小的的结点在FList中越靠前,则system.move()要操作的内存块也就越大。这就是我认为上述成批删除效率不高的原因,也是正向删除比逆向删除的耗时慢了慢了2680倍的原因。
对于成批删除,理想的算法是从index+len结点开始位置,向前移动count-index-len个结点,这样,就能够一次完成全部的结点移动,实现删除操作。这个思路非常好,至少我认为是这样。为此,我实现了下面的代码:
procedure CutList(aList:TList
left,len:integer);
begin
with aList do begin
System.Move(List^[left+len], List^
, (Count-left-len) *
SizeOf(Pointer));
count := count-len;
end;
end;
这段代码的功能是在TList.List这个Buffer中,将删除后的剩余结点直接移动到Left这个位置上,从而完成全部的移动操作。
然后,我们再设count := count-len;来使用个数减少,从而完成了成批量的删除。
好的,如果一切正常,算法的速度将大幅度提升!OHHH,美妙的想法!
但是,真的是这样么?我再用效率测试程序来测试了一轮,结果是这样的:
1. 测试数据为10万个结点,则逆向删除算法耗时为20.56毫秒,CutList()函数耗时9.69毫秒;
2. 测试数据为100万个结点,则逆向删除算法耗时为209.13毫秒,CutList()函数耗时98.01毫秒。
速度比逆向算法提高了一倍,而且应该注意到,CutList()的耗时仍然随数据量的增大而等比例的增大!!!而从CutList()函数的实现来看,数据量增大,算法耗时应该只增加极少才对。
要知道,只加快一倍速度的CutList(),并不是我所想要的!!!但为什么CutList()函数得不到更高的性能呢???

三、 面对私有域FCount,我咬牙切齿!!!
再次分析TList类的实现代码,发现count接口实现时,是用SetCount来写值,即CutList()函数中,
count := count-len;
一行的调用,实际上将调用
TList.setCount(count-len);
而TList.setCount()的实现代码如下:
procedure TList.SetCount(NewCount: Integer);
var
I: Integer;
begin
if (NewCount < 0) or (NewCount > MaxListSize) then Error(@SListCountError, NewCount);
if NewCount > FCapacity then SetCapacity(NewCount);
if NewCount > FCount //如果要增加Count的值,则调用Fillchar()来填充FList这个Buffer
//如果是要减少Count的值,则用for循环调用Delete()来删除结点
then FillChar(FList^[FCount], (NewCount - FCount) * SizeOf(Pointer), 0)
else for I := FCount - 1 downto NewCount do Delete(I);
FCount := NewCount;
end;
请注意看我在上面注释!OH!事实上,SetCount这个操作仍然将调用Delete()来删除各个结点(注意,Borland为了提高这个删除速度,也使用了逆向删除的算法来实现对Delete()的调用)。
所以,我们并不能在CutList()中得到更好的算法效率!——尽管,我们已经只要,仅仅只需要将FCount设成指定值即可,而并不需要再来一次成批的Delete()!
然而,FCount是一个私有域!!!从Delphi对OOP的实现机制来看,我只能通过SetCound()来实现写访问!!!在Classes.pas单元中的这段代码清楚地表明了这一切:
TList = class(TObject)
private
FList: PPointerList;
FCount: Integer;
...
protected
...
procedure SetCount(NewCount: Integer);
...
public
...
property Count: Integer read FCount write SetCount;
...
property List: PPointerList read FList;
end;
——看到这里,我咬牙切齿!!!

四、 跨域私有域保护!
但是接下来,我注意到一点,我们看到,Count读是直接读FCount的值,而写操作是调用SetCount()函数。仔细思考一下这个问题:
既然“Count读是直接读FCount的值”,那么,Count的地址是不是也直接指向FCount呢???
OK,且让我们用一段代码来证明它:
program testFixCount;
uses classes, Dialogs, sysUtils;
var t : TList;
pCount : ^Integer;
begin
t := TList.create;
pCount := @t.Count
//取得地址?
ShowMessage(IntToStr(pCount^));
t.Count := 10000;
ShowMessage(IntToStr(pCount^));
end.

很明显,pCount成功地得到了FCount的地址。哈哈,我们既然已经取得了一个变量在内存中的地址,那么,还有什么是不能做的呢???
我们可以很快完成这样的一段代码来测试对FCount私有域的直接写访问:
program testFixCount;
uses classes, Dialogs, sysUtils;
var t : TList;
pCount : ^Integer;
begin
t := TList.create;
pCount := @t.Count;
pCount^ := 10;
showMessage(IntToStr(t.Count));
end.
接下来,我们可以将curList()函数修改一下下了:
procedure CutList(aList:TList
left,len:integer);
var pCount : ^Integer;
begin
with aList do begin
System.Move(List^[left+len], List^
, (Count-left-len) *
SizeOf(Pointer));
//count := count-len;
pCount := @Count;
pCount^ := count-len;
end;
end;
OK! 我再次用那个可爱的效率测试工具测试了一下下,结果,哈哈,漂亮!——
1. 测试数据为10万个结点,则逆向删除算法耗时仍为20.56毫秒,准确地说是20333.25个微秒,而新CutList()函数耗时仅为1.08个微秒;
2. 测试数据为100万个结点,则逆向删除算法耗时为212.67毫秒(212668.22微秒),而这种情况下,CutList()函数耗时仅为1.26个微秒,比10万个结点略略多了一点儿!:)
快了NNNNNNN倍!!!
这才是我要的结果!一个不会随数据量增加而变慢的CutList()!

五、 突破更多的私有域……
存在的问题是,如果域不提供公用访问接口,那么,也就无法取到它的地址。这种情况下,我们是不是什么也得不到了呢?
仍然参考上述代码上TList的类定义,我们发现,在private域的定义中,FList和FCount被连续定义,从数据结构的角度来看,通常它们在内存中占用连续的空间。事实上,在Delphi中也正是如此处理的。因此,我们只需要得到FList的地址,并用“@List+SizeOf(PPointerList)”,就可以得到FCount的地址了。
我们用下面这段代码来测试不使用TList.Count而直接访问FCount的方法。这意味着,只要我们可以突破一个私有域,我们就可以通过地址计算的方法来突破全部的私有域!
program myTest;
{$APPTYPE CONSOLE}
uses classes, Dialogs, sysUtils;
var
t : TList;
pPrivateStrart : pointer;
pCount,pCapacity : ^Integer;
begin
t := TList.create;
pPrivateStrart := pointer(@t.List);
pCount := pointer(integer(pPrivateStrart) + sizeof(pointer));
pCapacity := pointer(integer(pCount) + sizeof(integer));
t.Count := 10000;
writeln('FCount:',t.Count);
writeln('FCapacity:',t.Capacity);
pCount^ := 10;
pCapacity^ := 1000;
writeln('FCount:',t.Count);
writeln('FCapacity:',t.Capacity);
end.

六、 其它
1. 由于直接访问私有域的方法超越了Delphi的对象保护,所以,访问私有域可能导致一些负面影响。比如,直接修改FCount的方法并不会使FList占用的Buffer的空间增大或者减小,也不会使Capacity的值发生任何变化。而在Delphi对于TList的类封装中,这三者之间是有联系的。我们有必要进一步地修改CutList()函数,再加一些代码来维护这种关系,以保证TList类操作的正常。完整的Cutlist()的实现可以在
http://www.delphibbs.com/delphibbs/dispq.asp?lid=650664
或者我的个人主页
http://www.doany.net/ 或 http://aiming.ynxx.com/
上找到。
2. CutList只实现对TList的连续区间的删除,对于任意Item的成批删除,creation-zy在DelphiBBS上有一段极其精彩的论述。它实现的AdvSoftDelete()函数可以极其快速地处理任意结点的成批删除。我建议你到
http://www.delphibbs.com/delphibbs/dispq.asp?lid=650664
翻阅一下creation-zy对该问题的论述。​
 
对于类的私有的方法,如果要访问可以,那就直接在继承这个类,然后把你需要访问的私有函数重新声明为public的就可以了!
 
to fenian
这个是访问私有变量,访问私有变量是没有问题。但用这个方法来访问私有方法。好像是行不同的。aminigoo的文我看过了。他的主页上不了。

我看了李入的<<Inside vcl>>应该方法和变量不是连续存放在一段地址。但是试了很多次,得到的地址和用delphi调式的看到的地址不一样。。。(学艺不精...)
 
to 蓝叶菱
呵呵!是我,但不是大侠。:)
你说的李维的《VCL 深入》是<<inside vcl>>吧,我旁边有一本,没找到...
 
to cqwty
对于类的私有的方法,如果要访问可以,那就直接在继承这个类,然后把你需要访问的私有函数重新声明为public的就可以了!

//重新申明是覆盖了基类的方法。
我是要访问基类的私有方法,并不是要重写方法:)
 
如果你非要认定不用重新写,直接访问的话,那我没有办法,就连提出oop思想的高人也没有办法解决这个问题,因为类的思想就是避免了对私有的数据的访问,保证类的完整性。可是你这么做,刚好和类的原则冲突了,没有办法的!
 
to cqwty
>>因为类的思想就是避免了对私有的数据的访问,保证类的完整性。
这句话没有错!但是在类的结构没有考虑好,又不能改写基类。确实很麻烦。
像C语言里面有友元这个概念,可以访问私有成员(对C不太熟,说错了请指正)
在pascal里面这个友元有局限性,非要在同一个单元里面。又不能改写这个单元...只能出此下策。想不到另外的路子了。

其实vcl框架有很多设计不合理的类,borland可能又没有精力去重新设计了....
 
只能继承
 
让你重新继承基类来把方法覆盖一下,无非就是在public区重新声明,这样不是能解决问题嘛?解决问题的办法有很多,为什么非要拿去和c++比较呢?如果一个语言都具备了其他任何一种语言的所有优点,那其他的语言还能存在嘛?照你这么说,你干脆和java比较好了,为什么borland公司没有想到把类型也做成类啊,java就是所有东西都是类。那delphi就无敌了!
 
to cqwty
我的意思不是要把方法覆盖,基类的私有方法在public中重新声明。有用吗?
我的意思要调用基类中的私有方法。并不是重新写一个。
 
我提出C里有友元这种概念。
是说明访问私有成员并不是你所说的,
&quot;因为类的思想就是避免了对私有的数据的访问,保证类的完整性。&quot;(访问私有方法是破坏类的完整性。)
 
程序员要是成天考虑这种问题,真有点走火入魔了.
 
况且比比有什么大惊小怪。
C#不是取Delphi和Java的所长生出来的么。不比能有C#出来吗?
 
这个问题已经用另外一种方法解决了(WriteProcessMemory)。目的是达到了。
这贴先不结,留着继续讨论一下
 
后退
顶部