当前位置:编程学习 > VB >>

关于循环引用:COM的interface还有高低之说呢?

MSDN里有一节讲循环引用,讲得挺吓人的。

所谓循环引用指的是:一个对象A的属性对象C,属性对象C通过C的parent属性又引用到对象A。这样A和C就构成循环引用。

在VB里循环引用会导致对象久久不能被释放,即使你set nothing之后。而如果你终止了整个应用之后,虽然对象可以被释放,但仍然会有很多孤儿对象留在内存里直到你关机。想起前一阵写树类的时候我到处都在用这种循环引用啊,汗。

里面讲到一个Excel的Button对象的例子:Microsoft Excel is written using C++ and low level COM interfaces, and it maintains the Parent properties of its objects without creating circular references.

这个的话,COM的interface还有高低之说呢?那像treeview这样的VB自带的ActiveX控件通常是用什么做的呢?



注:MSDN里的目录位置:Visual Tools and Languages\Visual Studio 6.0 Documentation\Visual Basic Documentation\Using Visual Basic\Component Tools Guide\Creating ActiveX Components\Creating ActiveX Components\Organizing Objects: The Object Model\Dealing with Circular References

--------------------编程问答-------------------- 这个循环引用指的是COM里智能指针,因为这个智能指针是基于引用计数的。如果互相拿着对方,就会导致无法释放。但如果你是拿着原始指针的话,就没关系。他说的Excel那个可能就是指原始指针。
--------------------编程问答-------------------- 上面写错了。

如果进程外的组件,那就组件关闭的时候,对象可以被释放,但仍然会有orphant对象存在;而进程内的对象基本上就要到应用终止。

但是到应用终止的时候,这些orphant对象所占用的内存还是可以被回收的。 --------------------编程问答-------------------- 恩。不知道我以前在VBA里写的是算原始指针还是引用指针。 --------------------编程问答-------------------- VB不懂了,VB里有指针吗?
--------------------编程问答--------------------

另外,没看懂,为何会有孤儿产生呢?不是所有objects的Terminate事件都被执行了么?

The objects client B was using will not be destroyed until the component closes. For example, if client A releases its Widget object, there will be no external references to the component. If the component does not have any forms loaded, and there is no code executing in any procedure, then the component will unload, and the Terminate events for all the objects will be executed. However, in the meantime, large numbers of orphaned objects may continue to exist, taking up memory.

--------------------编程问答-------------------- VB里没指针,呵呵。

不过,记得Tiger_Zhao好像说过一次赋值的时候地址传递的规则。我找找看。 --------------------编程问答-------------------- 如果我用的是最原始的地址,那就应该是你说的原始指针,对吧? --------------------编程问答-------------------- http://www.vbaccelerator.com/home/vb/Code/Techniques/Dealing_with_Circular_References/article.asp
这文章不错

--------------------编程问答-------------------- 找到以前Tiger_Zhao说的那个了,不过里面没讲对象赋值。我自己试了试,貌似对象赋值的时候用的是原始地址。


Sub daqfads()
    Dim c1 As Class2, c2 As Class2
    Set c1 = New Class2
    Debug.Print Hex(ObjPtr(c1))
    Set c2 = c1
    Debug.Print Hex(ObjPtr(c2))
End Sub
--------------------编程问答-------------------- 恩。这文章真不错。你是google到的么? --------------------编程问答--------------------
引用 9 楼 slowgrace 的回复:
找到以前Tiger_Zhao说的那个了,不过里面没讲对象赋值。我自己试了试,貌似对象赋值的时候用的是原始地址。

VB codeSub daqfads()Dim c1As Class2, c2As Class2Set c1=New Class2
    Debug.Print Hex(ObjPtr(c1))Set c2= c1
    Debug.Print Hex(ObjPtr(c2))End Sub


原始地址应该是一样的,不过你想直接用ObjPtr(c1)?你看一下8楼的那个 --------------------编程问答-------------------- All VB objects are either explicitly created as COM objects (if they are exposed objects compiled into an ActiveX binary) or are created as such internally within VB. All COM objects must follow the COM contract, which means they must implement at least three methods:

QueryInterface
AddRef
Release

The second two of these are of interest to how VB knows when to terminate an object. 

不知道internally within VB的objects是否也是默认遵循COM规范的? --------------------编程问答--------------------
引用 10 楼 slowgrace 的回复:
恩。这文章真不错。你是google到的么?


是啊,我google你那个MSDN的文章就找到这个文章了 --------------------编程问答--------------------
引用 12 楼 slowgrace 的回复:
 不知道internally within VB的objects是否也是默认遵循COM规范的?


看他的意思应该是的。 --------------------编程问答--------------------
引用 8 楼 ahao 的回复:
http://www.vbaccelerator.com/home/vb/Code/Techniques/Dealing_with_Circular_References/article.asp
这文章不错


受教了

周日都那么早起,膜拜下 --------------------编程问答-------------------- MSDN里的orphant objects也许是指:父对象的主引用已经终止,但是由于子对象有父对象的引用,导致父对象的terminate事件过程不会被执行,从而子对象也不会被销毁。这样子对象就成为了孤儿。 --------------------编程问答-------------------- 貌似这个网站到04年就停止更新了? --------------------编程问答-------------------- 谢谢ahao推荐的这篇文章http://www.vbaccelerator.com/home/vb/Code/Techniques/Dealing_with_Circular_References/article.asp。

分享并小结一下对循环引用的解决办法如下:

(1)引用父对象的时候用可以帮助定位到父对象的字符串。这个是MSDN推荐的。也是我打算采用的(其它的都太巧了,倒反让人觉得不靠谱)。

(2)引用父对象的时候直接用对象指针。就是通过ObjPtr得到父对象指针,然后通过CopyMemory直接引用到父对象,这样不会增加父对象的COM计数。

(3)每次引用父对象的时候,就直接再通过IUnkown接口把对象计数减一。You can cast your object to that type and call the Release method on it as soon as you have copied the object reference. 

(4)自己写个TearDown函数,每次终止父对象的时候,先把子对象对父对象的引用移除。这个看sonic_andy在另一个帖子里也推荐了这种做法。也许是比较常见的做法。

注:(2)-(4)在这个文章里有可下载的例子。
--------------------编程问答-------------------- 头晕.......

暂时用不到这知识,不看了- -! --------------------编程问答-------------------- 感觉循环引用还是会经常碰到的情况哈 --------------------编程问答--------------------
引用 16 楼 slowgrace 的回复:
MSDN里的orphant objects也许是指:父对象的主引用已经终止,但是由于子对象有父对象的引用,导致父对象的terminate事件过程不会被执行,从而子对象也不会被销毁。这样子对象就成为了孤儿。

他上面那段话还真看不太明白,什么所有object的terminate都调了,但又没有销毁?

引用 17 楼 slowgrace 的回复:
貌似这个网站到04年就停止更新了?

有点可惜,以前我找资料,也找到过这个网站,上面有些东西真不错的。
--------------------编程问答-------------------- 咳,16楼是我说的,不是MSDN说的

Say you have a class which manages a series of Worker objects that perform specific tasks, and the Worker objects need to notify their parent, or to interact with data held by the parent. What happens is this:

(1)When you create the Parent object in VB, AddRef is called. Parent now has a reference count of 1
(2)When Parent creates an instance of a Worker, AddRef is called on the Worker. Worker now also has a reference count of 1
(3)Worker gets a reference to the Parent object to allow it to notify or interact with the data. The Parent object reference count is incremented to 2.
(4)When Parent later goes out of scope, VB calls Release. This reduces the parent reference count to 1 (the reference held by the Worker). However, this now means that Parent still has a reference count of 1. Unless something can tell the Worker object to release its reference, both Parent and Worker will remain with reference counts until the application terminates. Remember that since the Parent does not enter the Class_Terminate method, there is nothing that will occur in the code to allow you to know that its main reference has been terminated and so it looks from Parent's point of view that it is still required, even though it is only a child class which is requiring it! --------------------编程问答-------------------- 我知道是你理解的,我说的是
If the component does not have any forms loaded, and there is no code executing in any procedure, then the component will unload, and the Terminate events for all the objects will be executed. However, in the meantime, large numbers of orphaned objects may continue to exist, taking up memory. 
--------------------编程问答-------------------- 恩,对。这句话我还是不理解。 --------------------编程问答-------------------- 用对象来表示属性,其实就是基于IDispatch接口LPDispatch属性,在LPDispatch中,还是尽量避免循环引用为好,如果真的使用了,那么一定要用一个模块级变量来表示它,然后要记得在析构函数里(VB可以在Sub Class_Terminate()或Sub UserControl_Terminate()过程中)显式释放该变量所引用的对象。 --------------------编程问答-------------------- 同楼上,在析构过程里面来处理。 --------------------编程问答--------------------
引用 18 楼 slowgrace 的回复:
谢谢ahao推荐的这篇文章http://www.vbaccelerator.com/home/vb/Code/Techniques/Dealing_with_Circular_References/article.asp。

分享并小结一下对循环引用的解决办法如下:

(1)引用父对象的时候用可以帮助定位到父对象的字符串。这个是MSDN推荐的。也是我打算采用的(其它的都太巧了,倒反让人觉得不靠谱)。


我觉得应该尽量避免循环调用,尽量尽量。

实在不成显式释放该变量所引用的对象 --------------------编程问答-------------------- to 楼上的楼上及楼上的楼上的楼上:

根据MSDN的说法,貌似一旦循环引用,析构函数将永远不被调用。 --------------------编程问答-------------------- 你可以看看Matthew著的<<高级Virtual Basic编程>>这本书, 我记得里边就有很大篇幅去介绍有关循环引用的解决方案 --------------------编程问答-------------------- 摘自MSDN:
循环引用的处理


通过分层结构中的包含关系可以从一个高层对象跟踪到它所包含的任何对象。

严格遵守包含关系的对象模型类似于树结构。任何给定的分枝(对象)都可以分为更小的分枝(从属对象),但是这些小分枝不能构成环路,也不能再次与树干或下一级的分枝相连。

如果一个从属对象的属性或变量拥有对包含其本身的对象的引用,就会导致带环的对象模型,或者叫做循环引用。

例如,Order 对象有一个包含 Contact 对象引用的 Contact 属性,该 Contact 对象代表放置次序的个体。而 Contact 对象又有一个包含 Company 对象引用的 Company 属性。

到现在为止,这个分层结构还是一棵树。但是,如果 Company 对象有一个包含 Order 对象引用的 MostRecentOrder 属性,就产生了循环引用。

注意   要避免这种情况,把 MostRecentOrder 属性设为文本关键字,从部件的 Orders 集合中检索 Order 对象。

Visual Basic 部件中的循环引用
考虑最简单的循环引用方式:Parent 属性。从属对象的 Parent 属性具有对包含该对象的对象引用。

例如,Microsoft Excel 的对象模型,Button 对象被 Worksheet 对象包含。如果有对该 Button 对象的引用,使用下面的代码就可以打印出包含它的 Worksheet 的名称。

'如果变量 btnCurrent 包含对 Microsoft Excel Button 对象的引用,下面的代码行显示
'包含该 button 的 Worksheet 对象的 Name 属性。
MsgBox btnCurrent.Parent.Name

Microsoft Excel 是用 C++ 和低级 COM 接口编写的,它保持其对象的 Parent 属性且没有造成循环引用。如果要用 Visual Basic 来实现这种关系,就得考察 Visual Basic 处理创建和毁坏对象的方式。

当某个对象不再被引用时,Visual Basic 就毁坏该对象。如果某对象的 Parent 属性有一个包含该对象引用的集合,就足以阻止该对象被毁坏。同样地,如果 Parent 属性有一个包含该对象引用的对象属性,该对象也不会被毁坏。

当父对象被毁坏时,实现其属性的变量不再有效,对象引用被释放。这样就可以终止该从属对象。不过,如果从属对象有 Parent 属性,Visual Basic 不会毁坏第一个位置的父对象,因为这个从属对象还在引用它。

从属对象也不能被毁坏,因为父对象也有其引用。在图 6.7 中用进程外部件说明了这种情形。

图 6.7   循环引用阻止对象被毁坏。



客户端应用程序 B 已经释放了对其 Widget 对象的引用。但该 Widget 对象有一个对 Knob 对象的引用,而 Knob 对象的 Parent 属性又指回该 Widget 对象,使得二者均不能被终止。

如果 Widget 对象有一个 Knob 对象的集合,而不是单个的 Knob 对象,也会出现类似的问题。该 Widget 对象拥有对 Knobs 集合对象的引用,而该集合对象又拥有对每个 Knob 对象的引用。每个 Knob 对象的 Parent 属性又指回该 Widget,形成了一个使得 Widget 对象、Knobs 集合和 Knob 对象都能存活的循环。

客户端 B 所使用的对象要到关闭该部件时才会被毁坏。例如,如果客户端 A 释放了它的 Widget 对象,这样不存在对该部件的外部引用了。如果该部件没有装入任何窗体,且任何过程都没有在执行其代码,则该部件被卸载,此时会执行所有对象的 Terminate 事件。不过,在此之前的时间内会存在大量孤立的对象,占据内存。

注意   如果在两个进程外部件的对象之间存在循环引用,这些部件永远都不会终止。

循环引用与进程内部件
如果把部件做成 DLL 实现,以便在客户端应用程序的进程中运行,那么避免循环引用就更加重要。因为进程内部件与客户端应用程序共享进程空间,对公共对象的引用不存在‘外部’和‘内部’之分。只要有一个对部件所提供的对象的引用,该部件就不会被卸载。

这就意味着循环引用将使进程内部件无限期地装载,而由孤立状态的对象所占据的内存直到客户端应用程序关闭才能重新分配。

详细信息   关于对象模型的更详细的信息,请参阅“ActiveX 部件的标准及指南”。
--------------------编程问答-------------------- 注意这里:
当某个对象不再被引用时,Visual Basic 就毁坏该对象。如果某对象的 Parent 属性有一个包含该对象引用的集合,就足以阻止该对象被毁坏。同样地,如果 Parent 属性有一个包含该对象引用的对象属性,该对象也不会被毁坏。  --------------------编程问答-------------------- 注意这里:
当某个对象不再被引用时,Visual Basic 就毁坏该对象。如果某对象的 Parent 属性有一个包含该对象引用的集合,就足以阻止该对象被毁坏。同样地,如果 Parent 属性有一个包含该对象引用的对象属性,该对象也不会被毁坏。  --------------------编程问答-------------------- 继续...

对象模型 
通过创建类模块并赋以属性和方法,就可以定义了类,接着就可以由该类创建任意数量的对象。如何记录所创建的这些对象呢?
记录对象最简单的办法,莫过于为创建的每个对象都声明一个对象变量。当然,这样对能够创建对象的数量就有了限制。
可以在某个数组或者集合中保持多个对象引用,就象在本章前面的“创建对象数组”和“创建对象集合”中所讨论的那样。
刚开始时,可能要定位窗体或标准模块中的对象变量、对象数组以及对象集合,就跟处理普通变量一样。但是,随着添加更多的类,可能会发现正使用的这些对象之间有明确的关系。
对象模型表达了包含关系
对象模型给出了基于对象程序的结构。通过定义程序中所使用的对象之间的关系,对象模型能够以一种使编程变得更容易的方式来组织对象。
一般来说,对象模型表达了这样一个事实:即某些对象是“更大的”,或者说比其它对象更重要一些—可以认为这些对象是包含其它对象的对象,或者是由其它对象所组成的对象。
例如,在编程时,可能创建一个 SmallBusiness 对象来作为程序的核心。可能想让这个 SmallBusiness 对象包含与其关联的其它类型的对象,比如 Employee 对象和 Customer 对象。可能同时也希望它包含一个 Product 对象。在图 9.11 中显示了该程序的对象模型。
图 9.11   对象模型
可以定义四个类模块,分别叫做 SmallBusiness、Employee、Customer 和 Product 类模块,并给它们中的每个添加合适的属性和方法,但是怎样在对象之间建立连接呢?有两种工具可以达到这个目的:即 Object 属性和 Collection 对象。下列这段代码显示了实现图 9.11 分层结构的一种办法。
' SmallBusiness 类模块中声明部分的代码。
Public Name As String
Public Product As New Product
Public Employees As New Collection
Public Customers As New Collection

第一次引用 Product 属性时,将创建对象,因为已经把它声明为 As New。例如,可以用下面的代码创建并设置 SmallBusiness 对象的 Product 对象的名称和价格。
' 一个标准模块的代码。
Public sbMain As New SmallBusiness
Sub Main
   sbMain.Name = "Velociraptor Enterprises, Inc."
   ' 在代码中首次使用 Product 变量时,创建 Product 对象。
   sbMain.Product.Name = "Inflatable Velociraptor"
   sbMain.Product.Price = 1.98
   .
   .   ' 初始化并显示主窗体的代码。
   .
End Sub

注意   用公共变量来实现一个对象属性并不简洁。如果在代码中的某处,将该属性设置为 Nothing 的话,可能会无意中将 Product 对象撤消。更好的办法是创建对象属性时,将之设置为只读属性,如下列代码所示。
'为更强健的对象属性所编制的代码。该属性的存储是私有的,
'因此不能从对象的外部将之设置为 Nothing。
Private mProduct As New Product

Property Get Product() As Product
   '首次调用这个属性时,mProduct 包含 Nothing,
   '因此 Visual Basic 将创建一个 Product 对象。
   Set Product = mProduct
End If

一对多对象关系
当对象之间关系是一对一时,对象属性可以正常工作。然而,经常出现的情况却是,某种类型的一个对象包含另一种类型的一些对象。在 SmallBusiness 对象模型中,Employees 属性是作为一个 Collection 对象来实现的,因此,这个 SmallBusiness 对象可以包含多个 Employee 对象。下列代码显示了怎样把新的 Employee 对象添加到这个集合中。
Public Function NewEmployee(Name, Salary, HireDate, _
ID) As Employee
   Dim empNew As New Employee
   empNew.Name = Name      '对象的隐含创建。
   empNew.Salary = Salary
   empNew.HireDate = HireDate
   '添加到集合中,并使用 ID 作为一个键。
   sbMain.Employees.Add empNew, CStr(ID)
   '返回对新 Employee 的引用。
   Set NewEmployee = empNew
End Function

在创建 SmallBusiness 对象所代表的企业雇员时,需要调用这个 NewEmployee 函数多少次,就可以调用多少次。任何时候,通过遍历 Employees 集合,都可将现有的雇员列出。
注意   再次说明,并没有非常强健的实现方法。比较好的实践是创建自己的集合类,并将它们按只读属性给出。这在“创建自己的集合类”中作了讨论。
提示   在 Visual Basic 的专业版和企业版中,包括 Class Builder 实用工具,利用这个实用工具,可以产生实现对象模型时需要的大量代码。Class Builder 可创建强健的对象属性和集合类,并且可以很容易地重新组织对象模型。
Parent 属性
具有对象的引用时,通过使用对象属性和对象集合,就可以到达这个引用所包含的对象。能够向分层结构的上层移动也是非常有用的,因为可以到达包含所引用对象的那个对象,。
向上移动通常是用 Parent 属性来完成的。Parent 属性返回对象容器的引用。关于对象模型定位的讨论,请参阅“用部件编程”中的“定位对象模型”。
可以在本章前面的“向类中添加属性”找到使用 Parent 属性的示例。
提示   当把 Parent 属性赋给集合中的对象时,不要使用对 Collection 对象的引用。这个对象真正的父是包含该集合的对象。如果 Parent 属性指向了该集合,将不得不使用两级间接指针,才能到达真正的父—也就是说,要用 obj.Parent.Parent,而不是 obj.Parent。
Parent 属性、循环引用,以及对象拆卸
使用 Parent 属性的一个最大问题是它们会造成循环引用。“较大”的对象具有对所包含对象的引用,而被包含的对象通过其 Parent 属性也有引用,这样就创建了一个环,如图 9.12 所示。
图 9.12   循环引用的一个例子
在这幅图片上有什么错误?去除这些对象的办法,是在用对象完成工作后,释放所有对它们的引用。假设对 SmallBusiness 对象的引用是在一个名为 sbMain 的变量中,就象在本节主题前面讨论的那样,可能会写出象下面这样的代码:
Set sbMain = Nothing

不幸的是,仍然有一个对 SmallBusiness 对象的引用—事实上,可能有许多引用,因为每个 Employee 对象的 Parent 属性都将包含对这个 SmallBusiness 对象的引用。
由于 SmallBusiness 对象的 Employees 集合包含有对每个 Employee 对象的引用,因此任何对象都不会被撤消。
TearDown 方法
一个解决这个问题的办法是,把 TearDown 方法添加给该 SmallBusiness 对象。这样,就可以将所有 SmallBusiness 对象的对象属性设置为 Nothing,同时也将所有 Collection 对象 (Employees, Customers) 设置为 Nothing。
当 Collection 对象被撤消时,Visual Basic 就把它所包含的所有对象引用设置为 Nothing。如果没有对包含在 Employees 和 Customers 集合中的 Employee 和 Customer 对象其它的引用,那么它们将被撤消。
当然,如果 Employee 对象是由更小的对象构成的,则将会存在其父对象所具有的同样的循环引用问题。在那种情况下,必须将一个 TearDown 方法赋给 Employee 类。要做的事情并不仅仅是将 Employees 的 Collection 对象设置为 Nothing,SmallBusiness 对象还将不得不首先遍历该集合,调用每个 Employee 对象的 TearDown 方法。
其它问题
即使这样,也并非所有对象都能被撤消。如果在程序的某个地方还有一些变量,这些变量仍然包含对 SmallBusiness 对象,或者对 SmallBusiness 对象所包含的任何对象的引用,那么这些对象将不会被破坏。程序中必须有一部分清理代码,以确保各个地方的所有对象变量都被设置为 Nothing。
为了测试是否正发生这种情况,可能需要将一些调试代码添加到对象中。例如,可以将下列代码添加到标准模块中:
' 全局调试集合
Public gcolDebug As New Collection

' 全局函数,赋给每个对象一个唯一的 ID。
Public Function DebugSerial() As Long
   Static lngSerial As Long
   lngSerial = lngSerial + 1
   DebugSerial = lngSerial
End Function

在每个类模块中,都可以加入类似下面这样的代码。每个类在“Product”出现的地方提供它自己的名字。
'调试 ID 的存储。
Private mlngDebugID As Long

Property Get DebugID() As Long
   DebugID = mlngDebugID
End Property

Private Sub Class_Initialize()
   mlngDebugID = DebugSerial
   '将一个字符串登录项添加到全局集合中。
   gcolDebug.Add "Product Initialize; DebugID=" _
   & DebugID, CStr(DebugID)
End Sub

Private Sub Class_Terminate()
   '删除字符串登录项,这样,就会知道对象不再存在了。
   gcolDebug.Remove CStr(DebugID)
End Sub

随着每个对象的创建,该对象将一个字符串放到全局集合中;当该对象撤消时,将删除该字符串。在任何时候,都可以遍历全局集合,看看什么对象还没有撤消。
详细信息   用 Visual Basic 的专业版或者企业版来创建 ActiveX 部件时,对象模型承担新的重要性,并且是另一个问题集合。请参阅《部件工具指南》中,“创建 ActiveX 部件”中的“部件设计的一般准则”。
--------------------编程问答-------------------- to lyserver and Modest, 看这个例子就知道MSDN所言非虚:

'Class CAnnaTree

Public Nod As CAnnaNode
Public TreName As String

Private Sub Class_Initialize()
    Set Nod = New CAnnaNode
    Set Nod.Parent = Me
    TreName = "throat"
End Sub

Private Sub Class_Terminate()
    Debug.Print "Class_Terminate", Time
    Set Nod.Parent = Nothing
End Sub

'Class CAnnaNode

Public Parent As CAnnaTree


'Module 1

Public Sub main()
    Dim ccc As New CAnnaTree
'    Dim ccc As CAnnaTree
'    Set ccc = New CAnnaTree
    Set ccc = Nothing
    Debug.Print ccc.TreName
    Debug.Print "set nothing", Time
End Sub



(1)如果你把Sub Main设为启动对象并按F5运行。观察几个debug输出的顺序,可以看到Set Nothing之后ccc的析构函数并未被调用,甚至ccc的属性仍然能够被访问,析构函数是直到应用终结才被调用的。
(2)如果只是在立即窗口测试Main,那析构函数压根儿就不会被执行。 --------------------编程问答-------------------- 另外,这里有一个奇怪的现象。

如果把Main的代码替换成下面这样,Set Nothing之后ccc就不再是可用的了,虽然此时ccc的析构函数并没有被执行:

'Module 1

Public Sub main()
'    Dim ccc As New CAnnaTree
    Dim ccc As CAnnaTree
    Set ccc = New CAnnaTree
    Set ccc = Nothing
    Debug.Print ccc.TreName  '这一行就会报错
    Debug.Print "set nothing", Time
End Sub


--------------------编程问答-------------------- 33楼后半部分说了TearDown的方法;之后还说了用全局调试集合的方法帮助调试,倒是个不错的办法,就是有点麻烦,你们平常这么做么? --------------------编程问答--------------------
引用 35 楼 slowgrace 的回复:
Public Sub main()
    Dim ccc As New CAnnaTree
'    Dim ccc As CAnnaTree
'    Set ccc = New CAnnaTree
    Set ccc = Nothing
    Debug.Print ccc.TreName
    Debug.Print "set nothing", Time
End Sub

使用new定义,这样的语句会重新激活对象
    Set ccc = Nothing
    Debug.Print ccc.TreName --------------------编程问答-------------------- TO AC,没看懂你在说啥 --------------------编程问答-------------------- 我说的意思是这样:
1.

dim newfrm as form1
set newfrm = new form1
set newfrm = nothing
debug.print newfrm.name '出错,因为newfrm此时没有初始化。

2.

dim newfrm as new form1
set newfrm = nothing '此时对象被释放
debug.print newfrm.name '因为使用了new定义对象,所以这一句代码的作用类似于set newfrm = new form1

--------------------编程问答-------------------- (1)如果你把Sub Main设为启动对象并按F5运行。观察几个debug输出的顺序,可以看到Set Nothing之后ccc的析构函数并未被调用,甚至ccc的属性仍然能够被访问,析构函数是直到应用终结才被调用的。
(2)如果只是在立即窗口测试Main,那析构函数压根儿就不会被执行。

-------------------------------------------------------------------------------
Set Nod.Parent = Me ,析构无法执行就是因为这里增加了一次node对tree的引用,所以你必须在孩子的析构里面解除引用。所以释放ccc之前,要先set ccc.node = nothing --------------------编程问答-------------------- (1)如果你把Sub Main设为启动对象并按F5运行。观察几个debug输出的顺序,可以看到Set Nothing之后ccc的析构函数并未被调用,甚至ccc的属性仍然能够被访问,析构函数是直到应用终结才被调用的。 
(2)如果只是在立即窗口测试Main,那析构函数压根儿就不会被执行。 

------------------------------------------------------------------------------- 
Set Nod.Parent = Me ,析构无法执行就是因为这里增加了一次node对tree的引用,所以你必须在孩子的析构里面解除引用。所以释放ccc之前,要先set ccc.node = nothing

'Class CAnnaNode

Public Parent As CAnnaTree



Private Sub Class_Terminate()
        Set Parent = Nothing
End Sub



Public Sub main()
    Dim ccc As New CAnnaTree
    
    Set ccc.Nod = Nothing
    Set ccc = Nothing
End Sub

代码修改类似 --------------------编程问答-------------------- mark --------------------编程问答-------------------- 完全看不懂 --------------------编程问答-------------------- --------------------编程问答-------------------- --------------------编程问答-------------------- --------------------编程问答-------------------- --------------------编程问答--------------------
引用 2 楼 slowgrace 的回复:
using C++ and low level COM interfaces

只要实现了 IUnknown 就算一个 "COM 对象",这大概就是所谓的 "low level"。
而 "VB 对象" 必须实现 IDispatch。 --------------------编程问答-------------------- 48楼的引用应该是
引用楼主 slowgrace 的回复:
using C++ and low level COM interfaces
--------------------编程问答-------------------- 回41楼 AisaC:  

(1)按照你的代码,即使不在CAnnaNode的析构函数里Set Parent = Nothing,CAnnaTree的析构函数也会被有效调用。

(2)甚至改成下面这样,也能有效调用CAnnaTree的析构函数

'Module 1

Public Sub main()
    Dim ccc As New CAnnaTree
'    Dim ccc As CAnnaTree
'    Set ccc = New CAnnaTree
    Set ccc = Nothing
    Set ccc.Nod = Nothing
    Debug.Print ccc.TreName
    Debug.Print "set nothing", Time
End Sub
--------------------编程问答-------------------- 所以,你的办法里有效的就是这一句:
Set ccc.Nod = Nothing


这一句实际上就是33楼所摘的TearDown方法的初级简化版:) --------------------编程问答--------------------
引用 50 楼 slowgrace 的回复:
回41楼 AisaC: 

(1)按照你的代码,即使不在CAnnaNode的析构函数里Set Parent = Nothing,CAnnaTree的析构函数也会被有效调用。

(2)甚至改成下面这样,也能有效调用CAnnaTree的析构函数

VB code'Module 1PublicSub main()Dim cccAsNew CAnnaTree'    Dim ccc As CAnnaTree
'    Set ccc = New CAnnaTreeSet ccc=NothingSet ccc.Nod=Nothing
    Debug.Print ccc.TreName
    Debug.Print"set nothing",TimeEnd Sub

这样做,在对象生存期间,CAnnaTree的析构函数并没有被调用!只有在main结束后才调用CAnnaTree的析构函数,如果改为:
Dim ccc As New CAnnaTree
Private Sub Form_Load()
'    Dim ccc As CAnnaTree
'    Set ccc = New CAnnaTree
    Set ccc = Nothing
    Set ccc.Nod = Nothing

    Debug.Print ccc.TreName
    Debug.Print "set nothing", Time
End Sub

ccc真成了孤儿了,只有程序结束才能释放。 --------------------编程问答-------------------- 你的理解貌似不对。你可以结合34楼的代码实际试试:) --------------------编程问答-------------------- 回39楼AisaC:这回明白你的意思了。查了MSDN,“如果使用 New 来声明对象变量,则在第一次引用该变量时将新建该对象的实例”。 --------------------编程问答-------------------- To zzyong:

之所以你引用的代码实际上会有效地调用析构函数,是因为先后两次Set Nothing,有效地把ccc的引用计数减为0了。 --------------------编程问答-------------------- 循环引用是基于“引用计数”的对象生存期管理可能出现的一个“副作用”,并非如楼上有些人所说是智能指针引入的问题。也并非COM所独有,我认为“引用计数”绝对应归入“设计模式”,COM只是恰巧使用而已。“引用计数”机制的一个一般性原则:持有引用必加计数,但这在某些情况下确实会引起棘手的“循环引用计数”问题,要避免这种情况,主要还要靠程序员引入其它辅助的对象生存期管理策略。比如:一个对象叫“毛”,一个对象叫“皮”。为访问方便,通常会在“毛”中持有“皮”的引用,并将“皮”的引用计数加一;在“皮”中持有“毛”的引用,但这时不必对“毛”的引用计数加一,否则,将出现“循环引用”,这里可以不加一的原因是,你知道“毛”的生存期永远从属于“皮”--“皮之不存,毛将焉附”。“引用计数”是好的,但它不是万能的,在特定的场景中,领域知识会帮助你避免循环引用问题。
有关引用计数更多的知识可参阅本人的博克:http://blog.csdn.net/DreamFreeLancer/archive/2009/05/20/4202951.aspx --------------------编程问答-------------------- 都很敬业、佩服! --------------------编程问答-------------------- 呃,感觉很复杂,好难,学习中,ING。。。。。。 --------------------编程问答-------------------- 除了顶,还是顶
补充:VB ,  非技术类
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,