循环引用与对象的存活期

在正常的程序操作中,全部释放一个对象的所有引用以后才会破坏该对象。这就是对象的存活期的含义。在这个主题中,为了说明这个问题,我们将以一种特殊的方式来使用 Thing 对象。

注意 创建一个 ActiveX DLL 示例需要分为几步,这个帮助主题只是其中一步。要访问该帮助主题,选择帮助主题“创建 ActiveX DLL”即可。

要在 Thing 类中添加 StuckOnMyself 属性,请按照以下步骤执行:

  1. 在“工程资源管理器”窗口中双击“Thing”,使类模块的代码窗口显示在最上面。

  2. 在声明部分添加下列代码:
    'StuckOnMyself 属性的私有数据。
    Private mthStuckOnMyself As Thing
    Private mblnStuckOnMyself As Boolean
    
  3. 在“工具”菜单中,单击“添加过程”打开“添加过程”对话框。在 Name 框中,输入 StuckOnMyself。单击“属性”和“公共的”,然后单击“确定”。

    修改属性过程如下所述:

    Public Property Get StuckOnMyself() As Boolean
    StuckOnMyself = mblnStuckOnMyself
    End Property
    
    Public Property Let StuckOnMyself(ByVal NewValue _
    As Boolean)
    mblnStuckOnMyself = NewValue
    If mblnStuckOnMyself Then
    Set mthStuckOnMyself = Me
    Else
    Set mthStuckOnMyself = Nothing
    End If
    End Property
    

    这段代码说明了属性过程的作用。当一个用户需要得到 StuckOnMyself 属性的值时,可以调用 Property Get 过程,这时只是简单地返回模块级的布尔变量 mblnStuckOnMyself 的值。

    当用户设置 StuckOnMyself 属性的值时,则调用 Property Let 过程。在为模块级的布尔变量 mblnStuckOnMyself 赋新值之后,Property Let 或者将模块级变量 mthStuckOnMyself 赋值为 Thing 对象 (Me) 的引用,或者将其设为 Nothing。

详细信息 对于部件提供的对象,要实现它的属性,最好的办法就是属性过程。请参阅“部件设计的一般准则”的“部件中属性的实现”。

循环引用的含义

如果两个对象相互引用,或者一个对象引用了它自己,那么就会产生循环引用的问题,StuckOnMyself 属性提供了这样一个例子。关于循环引用的详细信息,请参阅“部件设计的一般准则”中的“循环引用的处理”。

下一步向 TestThing 中添加新的代码,有选择地设置 StuckOnMyself 属性来说明什么是循环引用。同时,它还介绍了创建对象的另一种办法。

要通过 TestThing 测试 StuckOnMyself 属性,请按照以下步骤执行:

  1. 在“工程资源管理器”窗口中,双击“Form1”使窗体显示在最上面。

  2. 双击“Temporary Thing”按钮打开代码窗口,在 Command5_Click 事件过程中添加下列代码:
    '按钮 "Temporary Thing"Private Sub Command5_Click()
    Dim thTemp As New Thing
    thTemp.Name = InputBox( _
    "Enter a name for the temporary Thing", _
    "Temporary Thing")
    '如果标题为“Stuck on itself”的复选框被选中,
    '   则创建一个循环引用。
    If Check1.Value = vbChecked Then
    thTemp.StuckOnMyself = True
    End If
    End Sub
    

复选框不需要任何代码。如果 Check1 被选中,则 Command5_Click 事件过程会设置新的 Thing 对象的 StuckOnMyself 属性。

请注意,在这里并没有用 New 操作符来明确地创建新的 Thing,“Temporary Thing”按钮使用了一个声明为 As New 的变量,这样就可以隐含地创建对象了。下面的步骤将对此进行说明,同时,它还说明变量的作用域是如何影响对象的存活期的,以及循环引用对对象存活期的影响。

要用 TestThing 说明循环引用,请按照以下步骤执行:

  1. 按 CTRL+F5 键运行工程组。

  2. 单击“Create New Thing”。在输入框中输入名称 Long Term Thing,然后单击“确定”。在“立即”窗口中,注意从 Sub Main 和Thing的Initialize事件发来的消息。

  3. 单击“Temporary Thing”,创建一个只在短暂时间里存活的 Thing。由于引用该 Thing 的对象变量 thTemp 是过程级的变量,因此它的存活期(以及相应对象的存活期)就被限制在过程的执行时间内。

    首先看到的是一个“InputBox”,因为在为新的 Thing 对象的 Name 属性赋值之前,Visual Basic 必须先计算等号右边的代码。

    在向输入框中输入名称之前,请先看一看“立即”窗口中的内容。由于新的 Thing 对象尚未创建,因此没有 Thing 发来的 Initialize 消息。由于变量 thTemp 被声明为 As New,因此在调用 Thing 对象的属性或方法时将创建 Thing 对象(不会在这之前)。

  4. 在“InputBox”中输入任意的名称,然后单击“确定”。

    这时在“立即”窗口中将会看到两条消息,一条 Initialize 消息和一条 Terminate 消息。在把从 InputBox 获得的名称赋值给 thTemp.Name 时产生了 Initialize 事件。Visual Basic 发现 thTemp 包含的是 Nothing,于是创建 Thing 对象,并将对该对象的引用放入 thTemp 中。

    尽管 DebugID 属性的值已经被设置(在 Initialize 事件中最早完成的),但 Name 属性仍然是空的。这证实 Initialize 事件是最早发生的,先于所有其它代码的,也先于其它所有属性的设置。

    只有在完成所有这些之后,Visual Basic 才能把从 InputBox 函数获得的值赋给 Thing 的 Name 属性。一行代码可以引发很多的活动。

    事情还没有结束。在创建了 Thing 之后,Command5_Click 事件过程立即就结束了。变量 thTemp 离开了它的作用域,因此如同被设置成 Nothing 一样。现在没有对临时的 Thing 的引用了,因此它被破坏了。它的 Terminate 事件显示了它的属性,包括赋给它的名称。

  5. 按 CTRL+BREAK 键进入中断模式,然后按 F8 键进入单步模式。单击“Temporary Thing”进入 Command5_Click 事件过程。

    连续按几次 F8 键,一直运行到设置 Thing 的名称的那一行:

    在进入下一步之前,请猜一猜接下来执行的会是哪一行。

  6. 按 F8 键执行 InputBox 语句。在“InputBox”中输入任意的名称,然后单击“确定”。

    由于调试 ThingDemo 部件的环境与测试程序是同一个,Visual Basic 可以从测试程序中直接进入到部件中的代码。

    继续按 F8 键,运行 Initialize 事件、DebugID 属性以及 Terminate 事件中的代码。当运行到 Class_Terminate() 的最后一行时,按 F5 键返回到运行模式。

    要了解部件中发生事件的顺序,进程内调试是一个强有力的工具。

  7. 选中“Stuck on itself”,然后再一次单击“Temporary Thing”,创建引用自己的 Thing。在输入框中输入名称“Renegade”,然后单击“确定”。

    这时,在“立即”窗口中可以看到一条来自 Thing 的 Initialize 消息,但是没有 Terminate 消息。在变量 thTemp 离开作用域时,对象不会被破坏。因为仍旧存在对该对象的引用(在 StuckOnMyself 属性的 mthStuckOnMyself 变量中)。

    那么,在什么时候破坏原来的临时 Thing 呢?如果能够把 Renegade Thing 的 StuckOnMyself 属性设置成 False,那么就不会有更多的引用了,但是 TestThing 不能这样做,因为程序已经没有用来调用 StuckOnMyself 的引用了。

    这样对象就成为孤立的,在 DLL 卸载之前它将一直占用内存。在后面的步骤中可以看到,Renegade Thing 的 StuckOnMyself 属性中的循环引用将导致整个 DLL 保留在内存中。

  8. 单击“Release the Thing”,把命名为 Long Term Thing 的Thing 破坏掉,在“立即”窗口中观察它的 Terminate 消息,然后单击几次“Temporary Thing”。并为每个“临时的”对象输入新的名称。

    在观察第一个 Renegade 对象时,在“立即”窗口中将看不到这些对象的 Terminate 消息,因为循环引用将阻止破坏它们。

  9. 在“Thing Demo”对话框中,单击“关闭”框返回到设计模式。很快,象变魔术一样,所有的 Terminate 消息都将显示在“立即”窗口中。

    在 Visual Basic 关闭 TestThing 时,它还会关闭 ThingDemo。按照一定的顺序,它将清除 ThingDemo 的所有对象变量,其中包括 Renegade Thing 和它的后继者对自己的引用。

可以看到,在进程内部件中避免多余的对象引用是极为重要的。客户应用程序在使用部件时可以创建和释放几百个对象。如果对象都象 Renegade Thing 那样保留在内存中,性能将不可避免地降低。

详细信息 关于循环引用,请参阅“部件设计的一般准则”中的“循环引用的处理”,以及附录 B,“ActiveX 部件标准及指南”。

循序渐进

创建 ActiveX DLL 示例需要好几步,该帮助主题只是其中一步。

目的 请参阅
到下一步 在 ThingDemo 工程中添加窗体
从头开始 创建一个 ActiveX DLL