延续这个帖子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 是“混合”接口。因为那会儿我不是很理解接口的概念,所以就只好勉强接受了这种解释。
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)每一段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
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
只能从逻辑上给予推测,具体实现细节是 VB 秘密,不知道。
myctl继承自control,通俗点说,control里有的东西,myctl都有,而myctl有的,control就未必有,比如说你可以自己给myctl添加属性、方法、事件等,而这些属性、方法、事件,在control里却没有,如果把Set ctl = myCtl ,恐怕ctl就无法理解了,所以VB不允许这样的赋值纯个人理解
其实用下面的方法,很容易知道 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
你这种想法我也有,不过你的逻辑好像错了。你想想,如果像你说的,myCtl的东西比ctl的东西多,假设myCtl有个方法M1。Set myCtl=ctl之后,无非是把指向ctl的指针赋给myCtl,其实这块内存里存的还是ctl那堆东西。这时候我要是Call myCtl.M1,那不是乱了套了。所以,按你这个理解(其实也是我之前的思路)是此路不通的。
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
这种用法的?
(1)在类模块直接声明UserControl类型的对象myCtl的时候,得到的对象没有Extender部分的接口。
(2)用Controls.Add方法或者在设计视图直接添加控件的方法得到的UserControl对象newCtl是有两个接口的:UserControl的特别接口和Extender部分的接口。
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”才对啊?
MyControl[UserControl] {
Control,
Extender Object,
MyControl[Class]
}如果用对象指针的方式来分析3楼的前半部分代码,那么:ObjPtr(newCtl) = ObjPtr(ctl)
所以以下两个语句等价,都是取 newCtl 中的 MyControl[Class] 实例
Set myCtl = ctl
Set myCtl = newCtl
' 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之后它们的地址维持不变。这有点让人不解。
Type MyDll2Memory
pVTable0 '指向 MyDll2 的 VTable 指针'
...
pVTable1 '指向 ITest1 的 VTable 指针'
...
pVTable2 '指向 ITest2 的 VTable 指针'
...
End Type
而 bb、cc1、cc3 按照类型分别是指向 pVTable0、pVTable1、pVTable2 的指针。
bb Is cc1
(1)用户控件的接口实际上由用户控件类(包含用户控件特有的属性方法)和Extender对象两套接口构成。当以内部CTL方式引入用户控件类,并在代码里直接声明用户控件类对象的时候,我们得到的实际上只是用户控件类实例,该实例只具有用户控件类的接口,通过该实例无法直接访问到Extender对象接口的属性。
(2)在内部CTL方式下要想“双毒俱全”地引用Controls.Add的返回对象,就要“两套接口用两个变量”。
(3)只要是同一个对象,通常可以在实现的接口之间自由转化;但是如果不是同一个对象,那就不能转化了。
(4)子类对象的内存里分段存储了它的各个父类和它自己的数据。
(5)Set语句的实质是对被引用的对象计数加一,并传递对象地址。在同系列不同类的对象之间互相Set时,传递的是同一对象不同接口对应的不同对象的指针位置。同系同类的对象互相Set引用后,对象指针保持不变。
(6)检查两个对象是否为同一对象,用Is,而不要用ObjPtr。