VB6是建立在COM之上的,所有的COM对象都必须实现IUnknown接口。
IUnknown接口的IDL定义如下:
interface IUnknown
{
HRESULT QueryInterface(
[in] REFIID riid,
[out, iid_is(riid)] void **ppvObject);
ULONG AddRef();
ULONG Release();
}
QueryInterface用于接口查询,AddRef和Release用于引用计数。
COM是很复杂的东西,不是一两句话能够说得清楚的,感兴趣的话可以看看《COM原理与应用》、《COM技术内幕》、《COM本质论》等书籍。
VB6将复杂的COM都隐藏起来了,使得COM对象使用起来非常的简单:
Sub Main()
Dim o1 As Collection
Dim o2 As Object
Dim o3 As Control
Set o1 = New Collection
Set o2 = o1
Set o3 = o1End Sub
生成的汇编代码如下:
00401891 push ___vba@095E3C24 ; /Arg1 = Project1.___vba@095E3C24
00401896 call ___vbaNew ; \MSVBVM60.__vbaNew
0040189B push eax ; /Arg2
0040189C lea eax, [ebp-14] ; |
0040189F push eax ; |Arg1 => offset LOCAL.5
004018A0 call ___vbaObjSet ; \MSVBVM60.__vbaObjSet
004018A5 push dword ptr [ebp-14] ; /Arg2 => [LOCAL.5]
004018A8 lea eax, [ebp-18] ; |
004018AB push eax ; |Arg1 => offset LOCAL.6
004018AC call ___vbaObjSetAddref ; \MSVBVM60.__vbaObjSetAddref
004018B1 push ___vba@095E3C38 ; /Arg2 = Project1.___vba@095E3C38
004018B6 push dword ptr [ebp-14] ; |Arg1 => [LOCAL.5]
004018B9 call ___vbaCastObj ; \MSVBVM60.__vbaCastObj
004018BE push eax ; /Arg2
004018BF lea eax, [ebp-1C] ; |
004018C2 push eax ; |Arg1 => offset LOCAL.7
004018C3 call ___vbaObjSet ; \MSVBVM60.__vbaObjSet
004018C8 push 004018E6
004018CD lea ecx, [ebp-14]
004018D0 call @__vbaFreeObj ; [MSVBVM60.__vbaFreeObj
004018D5 lea ecx, [ebp-18]
004018D8 call @__vbaFreeObj ; [MSVBVM60.__vbaFreeObj
004018DD lea ecx, [ebp-1C]
004018E0 call @__vbaFreeObj ; [MSVBVM60.__vbaFreeObj
__vbaNew函数用于创建对象,__vbaObjSet用于将对象变量指向内存中的对象。
当用一个对象变量给另一个对象变量赋值时,如果两边变量的类型相同,Set赋值时使用__vbaObjSetAddref函数,使其指向同一个对象,并调用该对象的AddRef函数增加引用计数;如果变量的类型不同,Set赋值前调用__vbaCastObj函数,__vbaCastObj内部调用对象的QueryInterface查询对象是否实现了相应的接口,实现了的话则用__vbaObjSet赋值,否则抛出异常“类型不匹配”。
当变量超出作用范围时需要调用__vbaFreeObj函数,与函数的名称不符,该函数并不一定会释放对象所占用的内存,而只是调用对象的Release函数减少引用计数,只有当对象的引用计数为0时对象所占的内存才会被释放。