韩小明@xiammy的专栏

没水的地方挖井,有水的地方修渠

韩小明ID:xiammy
439324次访问,排名106好友12人,关注者67
毕业后一直在广联达工作
xiammy的文章
原创 174 篇
翻译 0 篇
转载 22 篇
评论 1133 篇
韩小明的公告
作者毕业于浙江大学,非常热爱体育运动。现在尤其热爱羽毛球运动。在休息时间非常热爱技术文章写作。
最近垃圾评论泛滥,为了不污染大家的视听,暂时关闭评论,请大家理解。
欢迎转载,但请注意,除非特别声明,本站采用Creative Commons License许可:署名,非商业。

最近评论
yb00k:感觉 这个还是个垃圾东西 适合IE7的变到IE8 就变样了 点都不规范 一点兼容性都不强....强烈支持 firefox
wuhuiran:我嵌入式数据库一直用BerkeleyDB,看到你的博文才知道还有一个SQLite,谢谢。BerkeleyDB不支持SQL
wuhuiran:我嵌入式数据库一直用BerkeleyDB,看到你的博文才知道还有一个SQLite,谢谢。BerkeleyDB不支持SQL
liquankun:瑞星还是不咋地!
白花了几个月的钱
外国的杀软不一定比国产的好!
但是国产的就是比不上国外的!
没办法!技术赶不上人家 还竟搞内讧
不经历大灾难 就不知道什么是团结!



正真的高手是不用杀毒软件的,没什么好不好的,是你自己技术不行而已
wangdei:http://www.bt285.cn BT下载 有300W部BT种子.
http://www.yaonba.com.cn NBA中文网 有200W条NBA直播
http://www.5a520.cn 小说520网 有300W部小说
http://www.bt285.cn/yazhou/ 亚洲BT 有BT亚洲
http://www.bjxlz.cn p……
文章分类
收藏
    相册
    图书
    链接
    宗刚的专栏(RSS)
    快乐学习(RSS)
    陈亮亮的专栏(RSS)
    朋友
    张恂论 OO
    言之有李(RSS)
    赵伟的小家
    存档
    订阅我的博客
    XML聚合  FeedSky

    原创 苛评VCL: 接口与TObject收藏

    新一篇: 苛评VCL: 失望的TMenu | 旧一篇: 谈谈SaaS的实现架构

    在李维的《inside VCL》中详细描述了VCL中TObject的地位。是的Borland的工程师们有心将Delphi语言做成pure language。所以你几乎可以看到TObject的所有pure pascal的实现。

    更重要的,你应该会发现。Delphi将代码的所有运行机制都暴露在我们面前。这也就是Delphi的TObject和C++中的Object以及C#的Object有很大不同的地方。

    Delphi将整个语言的机制都在TObject上实现了。消息机制、接口机制、面向对象机制(多态)等等你都可以从TObject的实现代码中看到运行的全部流程。

    不管TObject如何优秀,可是TObject正如Borland一样,在它身上总是看到受制于MS的影子。特别是接口机制。我有两点认为TObject的设计不好。

    1. 对象指针和接口指针并不是同一个地址
    2. 对象和接口不可以混用

    先说说我第一个不满意的地方。说是第一,并不表示是“最”的意思。只是代表“最先”的意思。

    在Delphi的TObject中,类在实现了接口之后,其创建的实例中,对象指针和接口指针并不是同一个地址。这个可能大家没有注意到,但是主要通过简单的例子(取两个指针的地址)就可以发现这个现象。

    你可能不会在意这个差异。是的,其实也没什么,不是同一个地址,代码照样可以工作。在对象的内存实例模型中,接口的指针直接存储在属性后面(如果存在继承,可能会出现交错的)。因为TObject设计的时候,两个地址不在一起,那么就存在两个问题:

    • 如果从对象转换到接口
    • 如何从接口转换到对象

    稍微知道对象模型的人,就应该知道对象调用虚拟方法表的过程。只有对象指针可以指向VMT。另外,在接口访问方法的实现代码的时候,也需要传入对象指针。这是为什么?我们知道,一个对象的方法中,有一个隐含的指针,我们一般称它为Self指针。只要不是class function,那么这个Self指针就是指向对象实例的指针。所以在代码的调用过程中,必然有这个转换。

    TObject提供的AS操作,可以完成第1个转换,但是可惜的是,TObject并没有提供一个公开的方法来负责第2个转换。这是我认为TObject在这方面设计不好的原因。(JCL代码库中有第2个转换的代码)

    第二个我不满意的地方就是TObject在生存期管理上,没有做到和接口一致。我相信这也是许多使用Delphi接口的同志们一致的想法。虽然我们现在看到Java和C#都已经做到这点,可不能不指出的是,在Delphi中,对象和接口不可以混用!

    我并不是奢求TObject的生存期可以自管理。毕竟,我还是习惯了“谁创建谁释放”的规则。可是一旦到了接口和对象混合使用的时候,就发生了问题。

    这虽然可以解释为接口是Delphi为了迎合COM而后加上的。我们今天却应该来设计一下一种规则,来解决接口混用的问题。我认为可以有一种简单的方式:TObject在Free的时候,发现其接口引用计数不为0的时候,不会Destroy。

    目前我们无法做到这点,这是因为Destroy是Delphi默认做的,也就是说,只要调用了Free方法,Destroy必然发生。我们无法完美地改变这个现实。也正因为此,我才认为必须在设计TObject的时候,将这个考虑进去!

    OK。尝试重新审视VCL中的各个基础类,其实有点大胆。所以用“苛评”这个词来做标题,表明这完全是我的苛刻,VCL的设计是非常棒的。不过也算是我使用6年Delphi的一点回报吧。以后还会继续苛评其他类。希望大家继续关注。

    发表于 @ 2007年02月02日 00:39:00|评论(loading...)|编辑

    新一篇: 苛评VCL: 失望的TMenu | 旧一篇: 谈谈SaaS的实现架构

    评论

    #fellow99 发表于2007-02-02 10:17:47  IP: 202.116.22.*
    在java/cpp中使用interface那是多么顺手多么自然的事情。换到delphi来,必须步步为营!
    我承认,我对delphi的接口实现不熟。。。好吧,我不在delphi中用接口了,我放弃。
    #xiammy 发表于2007-02-02 11:05:24  IP:
    呵呵,要用的时候,还是得用。
    #zhmnsw 发表于2007-02-02 11:37:49  IP: 218.12.29.*
    无聊的比较

    语言所处的层次不一样,为什么要以己之长来比别人的短处呢?
    VCL的设计同时兼顾了效率和扩展性,那么有些东西是需要牺牲的,它也比不过JAVA那种“反正我就是慢,不在乎那些牺牲”的言论,语言各有特点,各有各的适用范围,要是都一样了,干脆只出一种语言好了。

    .NET或JAVA里对象接口混用是建立在独立的垃圾回收机制上的,但VCL没有,不要告诉我,这一点就是VCL不如这两种语言的原因之一。

    虽然楼主恨铁不成钢的心情是可以理解的,但很多理由都不够充分,甚至是狡辩,没有杀伤力,就好比拿白痴能生活自理和很多医生不是院长来比一样。

    VCL是不断进步的,如果在将来,它可以放弃以前要兼顾的一些特性,就能够踢开很多限制,实现更多的特性。
    #zhmnsw 发表于2007-02-02 11:41:00  IP: 218.12.29.*
    如果非要深究,楼主还是跟李维老师探讨一下吧,我等小辈水平太有限了,不过别忘了把聊天记录贴上共享哦。
    以上言论如有不妥,敬请不吝赐教
    #ralf1999 发表于2007-02-02 11:44:39  IP: 222.131.201.*
    定义一个这样接可就可以解决你说的那个两问题
    IObject = interface
    function GetObjectInstance: TObject;
    procedure ReleaseObjectInstance;
    end;

    其实也没什么,DELPHI就是DELPHI,有她自己的风格,只是你还不了解。
    #Rainstorey 发表于2007-02-02 12:06:01  IP: 222.92.92.*
    真所谓有得必有失。世界并非完美,人也是,语言机制也一样。某一特性或许在你看来是累赘在别人看来就是完美。要用不同角度看问题,没有绝对的不好也没有决对的Perfect
    #zeroyet 发表于2007-02-02 12:36:43  IP: 218.249.11.*
    你要求的接口混用的情况出现,我觉得是你设计出了问题,不是接口没有设计好,就是对象没有设计好。
    还有关于释放的问题你的看看TInterfacedObject类了,TInterfacedObject类中Destroy是不会把还在引用的对象释放的,只是抛出一个异常(当然你可以改变这一点),你如果要用接口就从这个类派生。
    TObject是核心,他不会为一个特定的需求去设计什么,我觉得设计本应该如此。
    #hacker47 发表于2007-02-02 12:47:44  IP: 61.191.117.*
    用java和c++的思想来使用delphi/pascal,
    那是人的问题,不是delphi的问题。
    #cocoboy79 发表于2007-02-02 13:09:27  IP: 60.27.21.*
    VCL就现在来说,本来也就不是什么多先进的东西了。
    #SeaWave 发表于2007-02-02 16:11:09  IP: 124.161.135.*
    原文中说“对象指针和接口指针并不是同一个地址”,这句话没有交代明白,同一个对象实例,怎么可能不是同一个地址呢,这一点解释不通。
    事实上,做个简单的实验,发现对象指针和指口指针肯定是同一个地址。新建一个工程,在Form上放一个Memo控件:

    type
    IFoo = interface
    procedure Execute;
    end;

    TFoo = class(TInterfacedObject, IFoo)
    procedure Execute;
    procedure MyMethod;
    end;

    procedure Test(AFoo: IFoo);
    begin
    AFoo.Execute;
    end;

    procedure TForm1.FormCreate(Sender: TObject);
    var
    intf: IFoo;
    obj: TFoo;
    begin
    obj := TFoo.Create;
    obj.Execute;
    intf := obj;
    intf.Execute;
    Test(obj);
    Test(intf);
    end;

    procedure TFoo.Execute;
    begin
    Form1.Memo1.Lines.Add(Format('this=%d', [Integer(Self)]));
    end;

    结果当然是地址都相同。

    这段代码还说明了从对象转换到接口是很轻松的事情,连AS都不用(上面Test过程要求一个接口,由于obj这个对象实例实现了该接口,所以直接传递就可以了)。

    至于原文中所说的“如何从接口转换到对象”,首先想到的是为什么要把接口转换到对象?动机是什么?通常,编程是基于抽象,用接口的目的就是为了让上层不必了解底层细节,将派生类看生基类是很自然的做法,反过来把基类向下强制转换为派生类就很费解了。

    如果非要这么做,当且仅当你知道intf实现就是TFoo时,你可以用一个强制类型转换:

    TFoo(intf).MyMethod;
    #ralf1999 发表于2007-02-02 17:06:03  IP: 221.220.89.*
    在实际情况中,将INTERFACE转换成OBJECT也是很正常的事。你提供的方法:
    TFoo(intf).MyMethod;
    强制将INTERFACE转换成OBJECT,将跳过DELPHI的编译检查,如果转换错误将得到一个空指针。如果你的INTERFACE定义的层次很深的话也不能保证转换成功。正常情况下应当什么AS操作转换,但DELPHI中AS操作不能作用于INTERFACE到OBJECT的转换。

    DELPHI提供一个标准方法将INTERFACE转换为对象,即IInterfaceComponentReference接口,但此方法只针对从TCOMPONENT派生的类.但对于大规模复用的设计来说,使用TCOMPONENT作为最初的基类是唯一的选择。

    也可以参照IInterfaceComponentReference,定义自已的INTERFACE类型树,系统的解决这个问题。

    编程就像走钢丝,如果你掉下来,那一定不是因为钢丝太细。
    #maozefa 发表于2007-02-02 18:12:34  IP: 221.201.146.*
    Delphi中,由对象实现的接口后,对象和接口地址确实不相同,楼上举例显示的Self地址是对象的地址而不是接口地址,只不过说明对象方法映射到接口中了,从2个方面可以证明:
    1、直接显示对象和接口的地址:
    ShowMessage(Format('Obj=%.8x, Intf=%.8x', [Integer(Obj), Integer(Intf)]));
    可以看出它们的地址是不一样的。
    2、从BCB头文件代码可以证明,以TStreamAdapter为例:
    class DELPHICLASS TStreamAdapter;
    class PASCALIMPLEMENTATION TStreamAdapter : public System::TInterfacedObject
    {
    typedef System::TInterfacedObject inherited;

    private:
    TStream* FStream;
    TStreamOwnership FOwnership;

    public:
    __fastcall TStreamAdapter(TStream* Stream, TStreamOwnership Ownership);
    __fastcall virtual ~TStreamAdapter(void);
    (省略)
    private:
    void *__IStream; /* IStream */

    public:
    operator IStream*(void) { return (IStream*)&__IStream; }

    };

    当调用一个类似下面c++方法时候:
    void Load(IStream *stream);
    是这样传递参数的:
    TStreamAdapter *adapter = new TStreamAdapter(参数省略);
    Load(*adapter);
    也就是说不是直接传递adapter地址,而是用了一个取值符号*,而这个取值符号又是重载了的:
    operator IStream*(void) { return (IStream*)&__IStream; }
    取得是TStreamAdapter的一个私有指针__IStream而已。


    我上面只是实事求是指出Delphi接口和对象地址问题,并不代表我同意博主的观点,正如楼上zhmnsw所说:“我等小辈水平太有限了”。
    #cuizm 发表于2007-02-02 20:46:22  IP: 60.20.193.*
    路过

    http://www.egooglet.com/
    #xiammy 发表于2007-02-02 23:04:25  IP: 221.218.35.*
    不好意思,没有交代清楚。如果大家研究过TObject的方法,就会在InitInstance方法中发现端倪。
    <pre>
    class function TObject.InitInstance(Instance: Pointer): TObject;
    {$IFDEF PUREPASCAL}
    var
    IntfTable: PInterfaceTable;
    ClassPtr: TClass;
    I: Integer;
    begin
    FillChar(Instance^, InstanceSize, 0);
    PInteger(Instance)^ := Integer(Self);
    ClassPtr := Self;
    while ClassPtr <> nil do
    begin
    IntfTable := ClassPtr.GetInterfaceTable;
    if IntfTable <> nil then
    for I := 0 to IntfTable.EntryCount-1 do
    with IntfTable.Entries[I] do
    begin
    if VTable <> nil then
    PInteger(@PChar(Instance)[IOffset])^ := Integer(VTable);
    end;
    ClassPtr := ClassPtr.ClassParent;
    end;
    Result := Instance;
    end;
    </Pre>
    其中, PInteger(@PChar(Instance)[IOffset])^ := Integer(VTable);就是在对象实例中初始化接口的VMT指针。
    #cimil 发表于2007-02-03 08:55:12  IP:
    我说大哥,你起子号能不能规范点,上次那个就不说了,这个星子怎么跟日本女人似的,还是看书的时候看反了,改成子星要好多了。
    #daiyun 发表于2007-02-03 12:06:10  IP: 221.137.132.*
    vcl 开发出来的时候是什么年代的事情,C#开发出来的时候又是什么年代的事情,有可比性么,虽说都是出自同一人之手笔。
    #yrb 发表于2007-02-03 13:09:13  IP: 219.159.20.*
    哗众取宠,可恶!
    #StevenLee 发表于2007-02-03 16:54:06  IP: 221.218.192.*
    每种语言都有自己的优劣,取其长,避其短,这才是正道
    楼主这样用Delphi,只能说明楼主对Delphi的理解过于肤浅
    #aimingoo 发表于2007-02-04 00:06:15  IP: 61.173.115.*
    哎~~楼上的立意就不对~~一杆子打翻一船人,激起众怒又开架~何必何必~

    VCL归Delphi RTL管,COM归Win32内核管,Delphi中的Interface最早先被设计出来,是与COM有关的。你不能指望borland一开始就设计好一个与COM完全无关的接口机制,还要让它会满足“用于COM”这样一个设计需求。

    又,使用COM Interface,就不要让Delphi管理实例的生存周期。混用二者是灾难。但这是COM的机制决定的,根本不是、也不需要是要Delphi通过改变对象实例的结构来解决的问题。
    #xiammy 发表于2007-02-04 00:28:03  IP: 221.222.75.*
    关于语言的设计,我非常赞同Ruby的最小惊讶原则。当然了,这个原则在这里我要重新诠释一下,那就是,接口的使用不应该让不了解的人走到错误的路线上。是的,我们可以约束自己的调用规范。但是,请不要忘了,我们在讨论一个好的设计,而不是好的使用技巧。
    #xiammy 发表于2007-02-04 00:31:37  IP: 221.222.75.*
    to aimingoo:非常感谢你的提醒,看来我的修为还是要提高提高了。
    #edgethinking 发表于2007-02-05 09:15:06  IP: 58.60.128.*

    没看懂. 平时工作中用不到这些知识.
    #huanzhugege 发表于2007-02-05 09:26:33  IP: 61.48.59.*
    正是由于Delphi对接口支持的不完善性,我逐渐放弃了他
    #LastWorker 发表于2007-02-05 11:09:43  IP:
    支持aimingoo的观点!
    aimingoo说的其实在李维的书上说的很清楚了,楼主看来是只把注意力放到寻找"苛评"上了,忘了仔细看书了.
    不过楼主"苛评"的精神是值的鼓励的.
    #wr960204 发表于2007-02-05 14:24:59  IP: 61.235.75.*
    大概是楼主不太知道COM的实现机制吧。有些没办法,是由COM的机制限制的,不是一个FrameWork可以搞定的。
    还有接口固然是VMT,可是不能和对象的VMT混用实现接口和对象的地址一致。这个道理楼主想一下就明白了。
    还有你说的As操作符的反操作,可以非常容易的实现接口到对象的转变。只要给接口添加一个GetObject的方法,在方法的实现处直接写Result:=Self;就好了
    #xiammy 发表于2007-02-05 14:45:40  IP: 218.249.220.*
    优美的设计必然是平衡的。正如:E=mc^2一样,E转换成质量,质量也可转换成E。
    现在,提供了从对象到接口的AS,却没有从接口的对象的AS!
    另外,如果Delphi不提供AS,那么你是不是就在对象里增加一个方法叫GetInterface呢?这和增加GetObject是一个道理。不在于大家是不是知道怎么实现这个功能,而在于设计本身是不是考虑好这个问题。
    #mustangzhi 发表于2007-02-05 17:23:24  IP: 58.44.118.*
    对象指针和接口指针并不是同一个地址

    如果我没误解你的话,我觉得这点并没影响,
    但你讲这个设计是个缺点的话?

    我会问你对象指针和接口指针是同一地址的情况可能吗?

    对象和接口的指针地址都只是引用,是两个完全不同的变量,你不可能期望一个对象的引用和一个接口的引用使用同一个地址,就像你不能期望两个不同的变量使用同一地址一样.

    但接口指针和对象指针实际指向同一对象是可以的.
    这种情况是实际的对象存储在同一内存空间.

    如果没有另一个不同的对象引用,我们的对象又从哪里创建出来?
    接口的引用本身是不能创建对象的吧?

    你讲的其它几个缺点我更加是不知所云了.
    (同样呀,"我等小辈水平太有限了")
    #ylmg 发表于2007-02-05 23:35:06  IP:
    对象指针转为接口指针,这是可以理解的,但为什么要从接口指针转为对象指针呢?如果一定要这么做,很可能是设计上出了问题。
    顺便提醒一点,如果你是准备从delphi的对象模型中来找结论的话,有一个问题是无法回避的:如果这个接口的实现类不是delphi写的呢?
    就算这个实现类是delphi写的,你也得考虑一些问题,例如如果是用接口委托的方式实现的话,是不是会有问题。
    写过一个比较变态的函数,如下:
    {
    很变态的一个函数实现。
    如果一个类实现了某些接口,现在已经拿到了指向接口的指针,但如果需要拿到对象指针的话,
    需要用额外的比较麻烦的方法,这里提供一个函数可以方便拿到对象指针,该函数依赖于
    delphi 的内部编译实现,如果这个类是由其他语言实现的话,不可以使用该函数。
    因此通常情况下不建议使用该函数。
    }
    function GetObjectFromIntf(Intf: IUnknown): TObject;
    begin
    Result:= TObject(Integer(intf) - ((not PByte(PInteger(PInteger(intf)^)^ + 4)^ + 1) and $000000ff));
    end;
    #bluniu 发表于2007-02-06 10:51:18  IP:
    看到这么多谦虚的高手,我发现我用了3年的delphi(虽然现在不用了)还基本等于白痴级。没有各位究根刨底的精神。
    我发现其实大家都有个共同的,就是也同时在学习研究其它语言。。。

    如果以在座各位的水平,能组织起来一起做点东西,也是件不错的事,提倡互相讨论进步,不提倡互相鄙视。

    因为在delphi调用com事件响应时嫌搞得有点麻烦,而且还没实现。所以中断了学习。

    顺便弱弱的文句,如果也变态的使用同一接口在单线程实现两个不同的对象(虽然一般情况下无此必要),如果接口指针与对象指针一致,那是不是说两个对象。。。,好象有点不太对劲。或者是接口VMT中也有了两处记录,com原理不懂,朋友们给解释下。

    #ralf1999 发表于2007-02-06 11:31:15  IP: 221.220.91.*
    关于将INTERFACE转换为OBJECT,前提是:
    在不使用COM的情况下,只是将INTERFACE做为一般的OO技术使用在设计中。
    例如:
    由FACTORY创建的对象通常得到的是一个INTERFACE,当这个对象被交还给FACTORY消毁时需要可能需要由INTERFACE转换为OBJECT。
    当使用OBJECT POOL时,当传入对象时可能需要由INTERFACE转换为OBJECT。
    等等,另外一点是,VCL的设计是基于OBJECT的,当在你的设计中对VCL中的类进行包装时(包装到INTERFACE中),尢其是涉及到VISUAL OBJECT时,往往会发生INTERFACE到OBJECT的转换。

    INTERFACE转为OBJECT也不是什么奇怪的事,即然存在这么一项功能,那么就可以考虑:怎么用?用在哪?为什么用?不必急着贴标签,因为技术本来就是这么一回事,没有什么不可以的。
    #Myjiajia 发表于2007-02-06 12:45:36  IP: 222.70.187.*
    把这篇文章放到论坛里面,你就知道自己有多肤浅了.

    上面有位网友评论的好:
    哗众取宠,可恶!
    #lichaohui 发表于2007-02-07 20:38:12  IP: 58.240.64.*
    不要有所谓受制与MS 的心理阴影,

    这带来的好处就是对COM非常好的支持,
    COM是一个伟大的划时代的设计,不要小看MS,
    Delphi从中也得到了灵感,

    在MFC中,接口地址未必就是对象地址,
    因为涉及到多个虚拟方法表之间的切换,
    至于混用的问题,则是明知不能而为之,
    C++中的安全指针也有很多约束条件,你会故意的去违反这个
    约束条件吗?

    实际上,对比一下在c++ 和 delphi 中编写 COM对象和调用COm对象就会发现,TObject真是天才的设计。

    #HappyTeam 发表于2007-05-15 18:02:54  IP: 211.155.28.*
    各位高人,从TComponent与自定义的接口一并继承下来的类,怎样使用 接口转对象 操作?
    JCL里使用什么函数?
    #xiammy 发表于2007-05-15 22:41:06  IP: 221.216.18.*
    参见:《JCL中由接口获得对象的方法》http://blog.csdn.net/xiammy/archive/2007/05/15/1610610.aspx
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © 韩小明