微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

SetNewEnum = 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



版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐