延续这个帖子53楼的讨论。可以用UserControl类型的变量去引用control类型的变量,反之却不行。为什么? Option ExplicitPrivate Sub Form_Load()
    Dim myCtl As MyControl
    Dim ctl As Control
    
'*****这个可以
    Set ctl = Me.Controls.Add("Project1.MyControl", "ctl")  
    Set myCtl = ctl'*****这个不可以
    Set myCtl = Me.Controls.Add("Project1.MyControl", "ct2")  
    Set ctl = myCtl '这一句运行时错误:Type mismatch.End Sub赵老虎的简单解释是:因为不是按照通常意义的“继承”实现的,MyControl 是单一接口,Control 是“混合”接口。因为那会儿我不是很理解接口的概念,所以就只好勉强接受了这种解释。

解决方案 »

  1.   


    为了弄清楚这个问题,我做了以下实验。说明如下:
    (1)每一段VB Code表示位于一个工程里的代码。
    (2)AbstractClasses、PolyOne、PolyTwo都是ActiveX Dll,分别编译链接为Dll;PolyClient是引用了这3个Dll的标准EXE。
    (3)AbstractClasses以两个抽象类的方式定义了两个接口
    (4)PolyOne引用了AbstractClasses.Dll,其类模块MyDll1实现了接口一 ITest1
    (5)PolyTwo引用了AbstractClasses.Dll,其类模块MyDll2实现了接口一 ITest1 和接口二 ITest2
    (6)在PolyClient中观察对象互相转换的效果,没通过的我都注释掉了,并加了注释说明,请注意看一下这个例子中的Type Mismatch我是可以理解的。简单的说,实现接口少的对象是不能引用实现接口多的对象的。但是请注意aa和bb之间的转换,无论是aa到bb还是bb到aa,都是类型不匹配的。照我理解,0楼的MyCtl和Ctl之间的转换是更接近于aa和bb之间的转换的,所以0楼的问题还是没能通过这个实验得到解释。能否麻烦哪位前辈以这个实验的形式解释一下0楼的问题?'---------------------------------------------------------------------------------------
    ' Module    : AbstractClasses.ITest1
    '---------------------------------------------------------------------------------------Option ExplicitPublic Sub Method1()End Sub'---------------------------------------------------------------------------------------
    ' Module    : AbstractClasses.ITest2
    '---------------------------------------------------------------------------------------Option ExplicitPublic Sub Method2()End Sub'---------------------------------------------------------------------------------------
    ' Module    : PolyOne.MyDll1
    '---------------------------------------------------------------------------------------Option ExplicitImplements ITest1Private Sub ITest1_Method1()
        MsgBox "ones' method1"
    End Sub'
    'Public Sub M1()
    '    MsgBox "PolyOne's method"
    'End Sub
    '---------------------------------------------------------------------------------------
    ' Module    : PolyTwo.MyDll2
    '---------------------------------------------------------------------------------------Option ExplicitImplements ITest1
    Implements ITest2Private Sub ITest1_Method1()
        MsgBox "TWO implements i1"
    End SubPrivate Sub ITest2_Method2()
        MsgBox "TWO implements i2"
    End Sub'Public Sub M2()
    '    MsgBox "PolyTwo's method"
    'End Sub'---------------------------------------------------------------------------------------
    ' Module    : PolyClient.Module1
    '---------------------------------------------------------------------------------------Option ExplicitPublic Sub test()
        Dim aa As PolyOne.MyDll1
        Dim bb As PolyTwo.MyDll2
        
        Dim cc1 As AbstractClasses.ITest1
        Dim cc2 As AbstractClasses.ITest2
        
        Set aa = New PolyOne.MyDll1
        Set cc1 = aa
        Call cc1.Method1
        Set cc1 = Nothing
        Set cc2 = aa 'Type Mismatch 因为MyDll1没有实现接口1
        Call cc2.Method2
        Set cc2 = Nothing
    '    Set bb = aa 'Type Mismatch
        Set aa = Nothing
        
        Set bb = New PolyTwo.MyDll2
        Set cc1 = bb
        Call cc1.Method1
        Set cc1 = Nothing
        Set cc2 = bb '这里可以通过,因为MyDll2实现了接口2
        Call cc2.Method2
        Set cc2 = Nothing
    '    Set aa = bb 'Type Mismatch
        Set bb = Nothing
    End Sub
      

  2.   

    这里至少有三个类:
    1)Control
    2)MyControl[Class]
    3)MyControl[UserControl]
     “继承” Control
     包含 MyControl[Class] 实例,只是通过某种“代理”使得看起来好像同时“继承”了 MyControl[Class] 。前面提到了,在工程内,myCtl 声明的类型其实是 MyControl[Class]。
    所以将步骤分开来
    Option ExplicitPrivate Sub Form_Load()
        Dim newCtl As MyControl[UserControl]
        Dim myCtl As MyControl[Class]
        Dim ctl As Control
        
    '*****这个可以'
        Set newCtl = Me.Controls.Add("Project1.MyControl", "ctl")  
        Set ctl = newCtl
        Set myCtl = ctl '其实是取 newCtl 中的 MyControl[Class] 实例''*****这个不可以'
        Set newCtl = Me.Controls.Add("Project1.MyControl", "ctl")  
        Set myCtl = newCtl '其实是取 newCtl 中的 MyControl[Class] 实例'
        Set ctl = myCtl 'MyControl[Class] 不包含、不“继承”MyControl[UserControl],所以'
                        '这一句运行时错误:Type mismatch.'
    End Sub
      

  3.   

    你这里说的“继承”指的是Implements么?
      

  4.   

    是不是纯粹的 Implements 没有任何文档说明。
    只能从逻辑上给予推测,具体实现细节是 VB 秘密,不知道。
      

  5.   


    myctl继承自control,通俗点说,control里有的东西,myctl都有,而myctl有的,control就未必有,比如说你可以自己给myctl添加属性、方法、事件等,而这些属性、方法、事件,在control里却没有,如果把Set ctl = myCtl ,恐怕ctl就无法理解了,所以VB不允许这样的赋值纯个人理解
      

  6.   

    不对,只要是同一个对象,通常可以在实现的接口之间自由转化。
    其实用下面的方法,很容易知道 myCtl 和 ctl 不是同一个对象
    Private Sub Form_Load()
        Dim myCtl As MyControl
        Dim ctl As Control
        
        Set ctl = Me.Controls.Add("Project1.MyControl", "ctl")
        Set myCtl = ctl    Debug.Print Hex(ObjPtr(ctl))
        Debug.Print Hex(ObjPtr(myCtl))
    End Sub
      

  7.   


    你这种想法我也有,不过你的逻辑好像错了。你想想,如果像你说的,myCtl的东西比ctl的东西多,假设myCtl有个方法M1。Set myCtl=ctl之后,无非是把指向ctl的指针赋给myCtl,其实这块内存里存的还是ctl那堆东西。这时候我要是Call myCtl.M1,那不是乱了套了。所以,按你这个理解(其实也是我之前的思路)是此路不通的。
      

  8.   

    你得出推论条件是:"Set myCtl=ctl之后,无非是把指向ctl的指针赋给myCtl,其实这块内存里存的还是ctl那堆东西。",你这条件不正确。哪样赋值,不仅仅是只有一个指针的赋值吧?如果仅仅是指针的赋值,哪就很容易访问非法地址,如,两个对象分别在两个activeX exe里。
      

  9.   

    我说的是activeX DLL在EXE里也许能按照你那样推理。不过的话,ActiveX Control貌似永远都是进程内的。
      

  10.   

    晕,赵老虎未卜先知,他的代码证明了绝对不是“把指向ctl的指针赋给myCtl”
      

  11.   

    'form中
    Private Sub Command1_Click()
    Dim t1 As Class1
    Dim t2 As Class2
    Set t2 = New Class2
    Set t1 = t2
    Debug.Print ObjPtr(t1)
    Debug.Print ObjPtr(t2)
    End Sub'class1中
    Public Sub fun1()End Sub
    'class2中Implements Class1Private Sub Class1_fun1()End SubPublic Sub fun2()End Sub
      

  12.   

    我想他是把我的这句话“myctl继承自control”,理解为,“myctl与ctl是同一对象”
      

  13.   

    我的猜测和赵老虎的理解主要分歧在于:myctl是否继承自control,从他三楼的回复可以看出来
      

  14.   

    阿勇,请问3楼的代码是不是伪代码啊?还有MyControl[UserControl]、MyControl[Class]
    这种用法的?
      

  15.   

    我好像明白3楼的意思了。看下图:
    (1)在类模块直接声明UserControl类型的对象myCtl的时候,得到的对象没有Extender部分的接口。
    (2)用Controls.Add方法或者在设计视图直接添加控件的方法得到的UserControl对象newCtl是有两个接口的:UserControl的特别接口和Extender部分的接口。
      

  16.   

    但是我还是有一点不明白。3楼太远,不便于观察,所以把3楼代码再拷下来讨论:Option ExplicitPrivate Sub Form_Load()
        Dim newCtl As MyControl[UserControl]
        Dim myCtl As MyControl[Class]
        Dim ctl As Control
        
    '*****这个可以'
        Set newCtl = Me.Controls.Add("Project1.MyControl", "ctl")  
        Set ctl = newCtl
        Set myCtl = ctl '其实是取 newCtl 中的 MyControl[Class] 实例''*****这个不可以'
        Set newCtl = Me.Controls.Add("Project1.MyControl", "ctl")  
        Set myCtl = newCtl '其实是取 newCtl 中的 MyControl[Class] 实例'
        Set ctl = myCtl 'MyControl[Class] 不包含、不“继承”MyControl[UserControl],所以'
                        '这一句运行时错误:Type mismatch.'
    End Sub如果说“Set myCtl = newCtl ”是取了newCtl的接口之一;那么“ Set ctl = newCtl”为啥不是也取了接口之一呢?照我的猜测,通用的Control类型的对象应该是只有Extender的接口,那么“ Set ctl = newCtl”应该是只把newCtl中的Extender的接口取出来了,这样它下面的“Set myCtl = ctl”应该也由于接口不匹配而导致“Type mismatch”才对啊?
      

  17.   

    如果要用24楼的图来理解,MyControl[Class] 应该是右边的第三个组成部分,类似于下面的组成:
    MyControl[UserControl] {
        Control,
        Extender Object,
        MyControl[Class]
    }如果用对象指针的方式来分析3楼的前半部分代码,那么:ObjPtr(newCtl) = ObjPtr(ctl)
    所以以下两个语句等价,都是取 newCtl 中的 MyControl[Class] 实例
    Set myCtl = ctl
    Set myCtl = newCtl
      

  18.   

    为何ObjPtr(newCtl) = ObjPtr(ctl),但是ObjPtr(newCtl) != ObjPtr(myCtl) ?
      

  19.   

    MyControl[UserControl] 对 MyControl[Class] 的“继承”其实是自动封装,MyControl[Class] 实例是 MyControl[UserControl] 实例的一个成员,当需要从 MyControl[UserControl] 转换成 MyControl[Class] 时其实是返回了该成员(另一个对象)。
      

  20.   

    关于31楼引用的8楼的说法,我做了个测试,这个结果貌似符合你的说法,可是又不完全符合。'---------------------------------------------------------------------------------------
    ' Module    : PolyClient.Module1
    '---------------------------------------------------------------------------------------Option ExplicitPublic Sub test()
        Dim bb As PolyTwo.MyDll2
        
        Dim cc1 As AbstractClasses.ITest1
        Dim cc2 As AbstractClasses.ITest2
        
        Set bb = New PolyTwo.MyDll2
        Set cc1 = bb
        Debug.Print "After Set cc1 = bb, ObjPtr(bb)=", ObjPtr(bb)
        Debug.Print "After Set cc1 = bb, ObjPtr(cc1)=", ObjPtr(cc1)
        Call cc1.Method1
        Set cc1 = Nothing
        Set cc2 = bb '这里可以通过,因为MyDll2实现了接口2
        Debug.Print "After Set cc2 = bb, ObjPtr(bb)=", ObjPtr(bb)
        Debug.Print "After Set cc2 = bb, ObjPtr(cc2)=", ObjPtr(cc2)
        Set bb = cc2
        Debug.Print "After Set bb = cc2, ObjPtr(bb)=", ObjPtr(bb)
        Debug.Print "After Set bb = cc2, ObjPtr(cc2)=", ObjPtr(cc2)
        Call cc2.Method2
        Set cc2 = Nothing
        Set bb = Nothing
    End Sub
    其中各个类的定义同1楼。
    (1)可以看到无论是Set cc2 = bb 还是 Set bb = cc2都不会出错。这符合你在8楼的说法。
    (2)观察立即窗口cc2和bb的地址,发现:第一它们的地址的不同;第二每次set之后它们的地址维持不变。这有点让人不解。
      

  21.   

    首先阅读《高级 Visual Basic 编程》关于 VTable 的部分。一个 MyDll2 实例创建时,在分配内存上有如下数据结构(示意)
    Type MyDll2Memory
        pVTable0 '指向 MyDll2 的 VTable 指针'
        ...
        pVTable1 '指向 ITest1 的 VTable 指针'
        ...
        pVTable2 '指向 ITest2 的 VTable 指针'
        ...
    End Type
    而 bb、cc1、cc3 按照类型分别是指向 pVTable0、pVTable1、pVTable2 的指针。
      

  22.   

    判断两个对象是否相同用 Is 运算符
    bb Is cc1
      

  23.   

    谢谢,虽然还没看VTABLE,但是整个过程大致是没有推算不过去的了。
      

  24.   

    http://topic.csdn.net/u/20090826/16/6685B845-AE68-4C69-BC3B-ED94E0FFB57C.html这个帖子里的虚函数表和你说的VTable是一回事么?
      

  25.   

    严重感谢Tiger_Zhao!这两天写这个帖子的小结,才发现这中间绕了这么多弯,解了这么多惑。本帖有意义的回复如下:0楼、1楼、3楼(这一层是伪代码,要仔细看才能明白)、8楼、24楼-26楼、28楼、32楼-34楼甲虫的帖子有意义的回复在:0楼、12楼、14楼、17楼、37楼、40楼、45楼、46楼、51楼-53楼我把这两个帖子里学到的东西总结在这个博文里:在内部CTL方式下引用VB6 UserControl对象其中主要的结论如下:
    (1)用户控件的接口实际上由用户控件类(包含用户控件特有的属性方法)和Extender对象两套接口构成。当以内部CTL方式引入用户控件类,并在代码里直接声明用户控件类对象的时候,我们得到的实际上只是用户控件类实例,该实例只具有用户控件类的接口,通过该实例无法直接访问到Extender对象接口的属性。
    (2)在内部CTL方式下要想“双毒俱全”地引用Controls.Add的返回对象,就要“两套接口用两个变量”。
    (3)只要是同一个对象,通常可以在实现的接口之间自由转化;但是如果不是同一个对象,那就不能转化了。
    (4)子类对象的内存里分段存储了它的各个父类和它自己的数据。
    (5)Set语句的实质是对被引用的对象计数加一,并传递对象地址。在同系列不同类的对象之间互相Set时,传递的是同一对象不同接口对应的不同对象的指针位置。同系同类的对象互相Set引用后,对象指针保持不变。
    (6)检查两个对象是否为同一对象,用Is,而不要用ObjPtr。