IUnknown



VB6拾遗:IUnknown接口与COM对象

http://demon.tw/programming/vb6-repick-iunknown-com.html

VB6是建立在COM之上的,所有的COM对象都必须实现IUnknown接口。

IUnknown接口的IDL定义如下:

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规范规定任何组件、任何接口都必须从IUnknown继承,IUnknown包含三个函数,分别是 QueryInterfaceAddRefRelease这三个函数是无比重要的,而且它们的排列顺序也是不可改变的。

QueryInterface用于查询组件实现的其它接口,说白了也就是看看这个组件的父类中还有哪些接口类,AddRef用于增加引用计数,Release用于减少引用计数。引用计数也是COM中的一个非常重要的概念。大体上简单的说来可以这么理解,COM组件是个DLL,当客户程序要用它时就要把它装到内存里。另一方面,一个组件也不是只给你一个人用的,可能会有很多个程序同时都要用到它。但实际上DLL只装载了一次,即内存中只有一个COM组件,那COM组件由谁来释放?由客户程序吗?不可能,因为如果你释放了组件,那别人怎么用,所以只能由COM组件自己来负责。所以出现了引用计数的概念,COM维持一个计数,记录当前有多少人在用它,每多一次调用计数就加一,少一个客户用它就减一,当最后一个客户释放它的时侯,COM知道已经没有人用它了,它的使用已经结束了,那它就把它自己给释放了。

引用计数是COM编程里非常容易出错的一个地方,但所幸VC的各种各样的类库里已经基本上把AddRef的调用给隐含了,在我的印象里,我编程的时侯还从来没有调用过AddRef,我们只需在适当的时侯调用Release。至少有两个时侯要记住调用Release,第一个是调用了 QueryInterface以后,第二个是调用了任何得到一个接口的指针的函数以后,记住多查MSDN 以确定某个函数内部是否调用了AddRef,如果是的话那调用Release的责任就要归你了。 IUnknown的这三个函数的实现非常规范但也非常烦琐,容易出错,所幸的事我们可能永远也不需要自己来实现它们。

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 = o1
End 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时对象所占的内存才会被释放。



vb中有指针吗?(实例为双向循环链表

https://zhidao.baidu.com/question/47259296.html

VB的指针挺简单的,用着也很方便,其实对象变量就可以看成是指针,当你用Set A=Obj时,A就是指向Obj的地址,不用API就可以,当然用API可以实现更为高级的结构。 
给一个例子,一个用VB实现的双向循环链表。有链表的生成,删除和结点的插入。 

先定义一个结点类,类名为Node,代码为: 
Option Explicit 
Public pNext As Node 
Public pPrev As Node 
Public data As Single 

Private Sub Class_Initialize() 
    Set pNext = Nothing 
    Set pPrev = Nothing 
End Sub

Private Sub Class_Terminate() 
    Set pNext = Nothing 
    Set pPrev = Nothing 
End Sub 
再添加一个窗体,窗体上添加两个列表框,list1和list2,窗体的代码为: 
Option Explicit 
Private pHead As Object 
Private pV As Object 

Private Sub Form_Load() 
Dim i As Integer 
Set pHead = New Node
    Call CreateLinkList 
    Call InsertNode(pHead, 503) 
    Call InsertNode(pHead, 1.875) 
Call InsertNode(pHead, -3.675)
    For i = 1 To 100 
        Call InsertNode(pHead, -1 * i) 
    Next 
    Call PrintList 
    Call DeleteList 
End Sub 

Public Sub CreateLinkList() 
Dim p As Node 
Dim nLoop As Integer 
Static pLast As Node 
pHead.data = 0 
Set pLast = pHead 
For nLoop = 1 To 501 
    Set p = New Node 
    p.data =搜索 nLoop 
    Set pLast.pNext = p 
    Set p.pPrev = pLast 
    Set pLast = p 
Next 
Set pLast = Nothing 
Set p.pNext = pHead 
Set pHead.pPrev = p 
Exit Sub 
End Sub 

Public Sub PrintList() 
List1.AddItem "Forwards" 
Set pV = pHead 
Do 
List1.AddItem pV.data
    Set pV = pV.pNext 
Loop While Not pV Is pHead 

List2.AddItem "Backwards" 
Set pV = pHead.pPrev 
Do 
    List2.AddItem pV.data 
    Set pV = pV.pPrev 
Loop While Not pV Is pHead.pPrev 
End Sub 

Public Sub DeleteList() 
Dim p As Node 
Set pV = pHead 
Do 
    Set pV = pV.pNext 
    Set p = pV.pPrev 
    If Not p Is Nothing Then 
Set p.pNext = Nothing
        Set p.搜索pPrev = Nothing 
    End If 
    Set p = Nothing 
Loop While Not pV.pNext Is Nothing 
Set pV = Nothing 
Set pHead = Nothing 
End Sub

Public Sub InsertNode(head As Node, data As Single) 
Dim p As New Node, q As Node, prev As Node 
p.data = data 
Set q = head 
Set prev = head.pPrev 
While ((q.data < p.data) And Not q.pNext Is head)
Set q = q.pNext
Set prev = prev.pNext
Wend
If Not q.pNext Is head Then
Set p.pNext = q
Set p.pPrev = prev
Set prev.pNext = p
Set q.pPrev = p
If q Is head Then
Set head = p
End If
Else
Set p.pNext = head
Set p.pPrev = q
Set head.pPrev = p
Set q.pNext = p
End If
End Sub
一个双向循环链表就形成了,List1中是正向遍历的结果,List2中是反向遍历的

结果类的构造器Class_Initialize()过程,类的析构Class_Termainate()过程,结点内存的分配和回收都由类自身完成,还有多态,pHead As Object;Set pHead =New Node;Set pHead.pPrev = p;指向基类的指针指向了子类,并调用了子类的属性,是不是挺像C++的代码?

链表有了,二叉树,由临接表构成的图等数据结构都很容易实现了吧,实际上用VB能构造很复杂的数据结构,上面的代码只是简单的示例,实际可以做的更完善。

另外,VB6也能够生成真实的地址。三种未正式公布的VBA方法VarPtr,ObjPtr,和StrPtr(实际上是指向运行DLL同一入口的三个不同的类型库别名)就可以用来建立指针,使用address=ObjPtr(Obj)就可以获得对象的地址,Obj为需要地址的对象,而Address为一个long型变量,其中放置了对象的地址,使用VarPtr(产生变量的地址和UDT),StrPtr(产生字符串的地址)和ObjPtr (产生对象的地址)可以构造真实的,非常复杂的数据结构。

上面三个方法并没有在Microsoft的正式文档资料中公布(包括MSDN),但查看VB6的基本动态运行库MSVBVM60.DLL可以发现这三个方法:

[entry(0x60000006),hidden]
long __stdcall VarPtr([in]void* Ptr);
[entry(0x60000007),hidden]
long __stdcall StrPtr([in]BSTR Ptr);
[entry(0x60000008),hidden]
long __stdcall ObjPtr([in]IUnknown* Ptr);
类似这样的隐藏方法还有不少,实际上VB6的功能是相当强大的


VB6编程技术大全中实例(并非原文,稍微编辑一下)

§ 7-4-2 集合类

集合类保存了私有集合变量的引用,并连接到外部界面,以使客户代码相信它正在交互处理真正的Collection。基于之上的目的,要增强已有的类(Invoice)的功能,就要再建立一个集合类(InvoiceLines)


Private m_InvoiceLines as New Collection


Sub Add( newItem As CinvoiceLine, Optional Key As Variant, Optional Before As Variant, Optional After As Variant,)

       m_InvoiceLines.Add newItem, Key

End Sub

Sub Remove( index As Variant,)

       m_InvoiceLines.Remove index

End Sub

Function Item(index As Variant) As CInvoiceLine

       Set Item = m_InvoiceLines.Item(index)

End Function

Property Get Count( ) As Long

       Count = m_InvoiceLines.Count

End Property

还要做两件事,才能让集合类完美模仿标准的Collection:1)支持缺省项,2)支持枚举项

1)使Item成为缺省成员

当处理Collection对象时,代码中常常省略Item成员的名字。为了在集合类中也支持此特性,只需要让Item成为类的缺省成员,可以从Tools主菜单中选择Procedure Attribute,在对话框顶部的组合框中选择Item,扩展对话框(即Procedure Attribute对话框下端显示增加选项),并在ProcID字段中键入或在下拉式列表中选择0(缺省)

§ 6-2-5 属性 一节中

    2、类的缺省成员

大多数VB控件及内部对象都有一个缺省属性或者方法。例如Collection有一个缺省的Item方法。如果在表达式中省略了成员名,VB这是指特定的对象,所以这种数据项认为是缺省成员。按照下列方法,甚至可以对自己的类实现同样的机制:

a、在代码窗口中的属性或者方法定义处单击鼠标,从“工具Tools”中调用过程“属性Attribute”,然后如果数据项的名字还没有显示,就在组合框的最顶端选择数据项的名字。

b、另一方面,按下F2打开Object Browser,在最左端的窗格中选择类模块的名字:在最右端的窗格中,在要成为缺省成员的数据项上单击鼠标,然后从出现的弹出菜单中选择Property,如图6-6

c、一旦所感兴趣的数据项以高亮方式显示在最顶端的Name组合框中,单击“高级Advanced”按钮,扩展Procedure Attribute对话框,如图6-7

d、在Procedure ID组合框中选择“缺省default”项;另一方面还可以在组合框的编辑区内输入0

e、单击OK按钮,确定生效并关闭对话框。在Object Browser中,在该成员名旁边已经出现一个小圆指示符。这证明它已经成为类的缺省成员。

一个类只能有一个缺省方法或者属性。如果出现第二个缺省项,报错!

2)为枚举项增加支持

首先添加下面的过程到类模块中

Function NewEnum( ) As IUnknown

       Set NewEnum = m_InvoiceLines.[_NewEnum]

End Function

然后激活Procedure Attribute对话框,选择NewEnum成员,为它分配ProcID等于-4,选择复选框Hide This Member,关闭对话框

OLE协议规定类必须通过一个ProcID等于-4的函数提供此枚举器对象




关于《过程属性对话框》

Setting the Procedure Attributes

https://msdn.microsoft.com/en-us/library/aa260636(v=vs.60).aspx



你可能感兴趣的:(Visual,Basic,6.0)