【问题标题】:VBA Reference counting - Object destructionVBA 引用计数 - 对象销毁
【发布时间】:2012-07-09 12:38:40
【问题描述】:

最近我遇到了一个让我很困惑的问题;它让我很忙,我在网上找不到透明的解释。
它与 Excel 对象的破坏有关(我一直在使用它,并且以前从未真正质疑过)。

导致我的问题的背景:
对于常规对象,您可以使用关键字 SET 和 NEW 实例化对象。 例如:

Set classInstance = New className

每当我们以这种方式实例化时,对象就会在堆内存中创建,并且引用计数器增加 1。
如果我不添加更多引用,以下语句会将引用计数归零:

Set classInstance = Nothing 

当引用计数变为 0 时,对象被销毁并从内存中清除,“classInstance”指向 .

我读过的内容:
当我们使用“CREATEOBJECT”函数时,它返回一个对 COM 对象的引用。

Set oApp = CreateObject("Excel.Application")

即使我们可以说:

Set oApp = nothing 

对象的引用计数将变为 0,oApp 将不再指向该对象。

我的问题
1)为什么这种类型的对象需要在对象实际从内存中删除之前调用方法.Quit?
添加对需要 .close 方法的工作簿对象(workbooks.add 或 workbook.open)的引用时也是如此。 为什么将引用计数归零时这些对象不能被自动销毁?
例如,当我们说:

set oRange = nothing 

2) 还有必要说:

oApp.Quit
set oApp = nothing 

由于应用.Quit时Application对象已从内存中清除,因此不再有对象可释放。
我能想出的唯一原因是,为什么 oApp 在退出后会设置为 Nothing,是因为它可能指向未使用的内存位置(在堆上),如果重新分配该内存,以后可能会导致混乱(尽管在 VBA 中我觉得这很难想象)。 我在质疑自己这个结论是否正确,我希望得到知道答案的人的确认。
请告诉我我是否看错了。

3) 他们在 VBA 中所说的“对对象的引用”(例如上面代码中的 oApp),我将它们视为 C 中的指针变量。 使用此语句是否安全或再次使用,我是否看错了?

通常不难申请 .Quit 并设置为空,但收到有关该主题的一些准确信息会很高兴。这样我就可以 100% 地知道我为什么要这样做。

【问题讨论】:

  • + 1 个很好的问题 + 问得很好。很少能找到这么详细的问题:)
  • 在大多数情况下,我认为您不需要使用Set .. Nothing(请参阅here

标签: excel vba


【解决方案1】:

好问题:)

Excel 控制其对象的创建。同样,它也控制它们的破坏。

设置oApp = Nothing 只会破坏对象引用。它不会删除应用程序。要销毁 Excel 对象,您必须使用它的 .Quit 方法。

无论何时,Set x = Nothing,其相关对象的名为 x 的引用(指针)都会被删除。这并不意味着对象本身将从内存中删除。 对象是否会从内存中删除,取决于多种因素。

  1. 是否有更多的引用指向同一个对象。如果有,则不会删除该对象。引用计数必须为零。
  2. 该对象的析构函数的内部实现。

.Quit 方法被定义为优雅地删除所有 excel 已分配的内存对象,并自行关闭。

这类似于在 VB6 的窗体上调用 Close。以vb6中的表单为例。

Dim f As Form
Set f = Form1
f.Show

'
'~~> Rest of the code
'

Set f = Nothing

这会破坏表格吗? :)

跟进

问题 2 怎么样?谢谢 – Kim Gysen 14 分钟前

它可能与此处显示的不完全一样,编译器优化可能会使事情的表现有所不同......但这是起作用的基本概念。

【讨论】:

  • 感谢您的回复。很多时候你读到对象在引用计数器归零时会自动销毁(没有 GC);这就是让我感到震惊的原因。我知道这个陈述是不完整的(甚至部分不正确),因为它还取决于对象析构函数的定义(这实际上是合乎逻辑的)。因此,对于问题 3,我的假设是正确的,因为我从您的回复中扣除。问题2呢?谢谢
  • 让我用一张图片来回答你的问题。如果您仍有疑问,请在此之后告诉我 :)
  • 直到您不完全满意时,您才需要接受回复。 ;) 让我来看看你的问题。我会尽快更新我的答案。
  • 好的,我不会更新问题,而是在这里发表我的评论:活动的指针但其对象已被破坏,称为“悬空指针”。与 C/C++ 类型语言不同,指针的实现隐藏在 VBA 中(实际上所有的 VB6 和 VB.NET 也是如此)。所以我不确定你在问什么。也许该内存在没有将指针设置为空的情况下被回收,或者它可能会一直保留到指针超出范围。这取决于运行时如何处理它,我不太确定它的内部结构。
  • 确实,你击中了我的目标,悬空指针;所以这就是不将指针设置为空的情况。有时对于设置为空是否“必要”存在普遍的混淆;根据您的回复,我得出结论,这绝对是一种很好的做法,因为通常要避免悬挂指针,因为在其他语言中这可能会带来麻烦。谢谢悉达思!
【解决方案2】:

您问题的第 2 部分非常有趣,非常值得扩展答案。

这将涵盖三个关键点:

  • 对象和对象变量;
  • 解除对象时的陷阱;
  • ...在 Excel 2013 中对 Application 对象进行引用计数方面的一项重要变化。
但是,如果您想要一个简短的答案,那就是:“并非所有对象都是平等的”。

现在继续阅读...

有些对象是在您的 Excel 会话“自己的”内存空间中创建的,它们的内存分配由您的会话控制;一些对象具有在对象变量被解除后存在的持久组件;有些没有:

Set oDict = CreateObject("Scripting.Dictionary")
Set oWShell = CreateObject("Shell.Application")

在这两种情况下,都会分配内存,实例化对象变量(以及它们指向方法和属性的指针的 vTable),在您解除它们之前,它们都是您的命令:

Set oDict = Nothing
Set oWShell = Nothing

而且,在解雇时,他们没有留下任何痕迹。

但是 this 对象是持久的:

Dim oWbk as Excel.Workbook
Set oWbk = Application.Workbooks.Add
...您已经创建了一个新的工作簿对象,如果您使用 Set oWbk = Nothing 关闭对象变量,您将看到新的工作簿对象仍然作为可见的存在于用户界面中。

您实际创建的是一个 Workbook object - 一个带有活动工作表和与之配套的完整用户界面的工作簿窗口 - 以及一个 Workbook object variable - 一个程序员的 COM 接口,工作簿对象的方法和属性表 - 您可以使用命名实体 'oWbk' 在代码中对其进行操作。

取消 oWbk 对象变量会删除该框架,但 Workbook 本身仍然存在:您已经创建了一个 Workbook 对象,它是您的。

对象不仅仅是它的对象变量,解除变量并不会破坏对象:它只是解除了一个接口,一个方法和属性的框架,您可以使用它在代码中操作对象.

关闭工作簿,无论是否保存文件,都应该自动关闭对象变量并清除为该接口分配的属性、方法和属性的内存:

'try this:
oWbk.Close SaveChanges:=False
' or maybe this:
Application.Workbooks(Application.Workbooks.Count).Close SaveChanges:=False 
...也就是说,您希望这两个命令都调用 Set oWbk= Nothing - 特别是 oWbk.Close 命令 - 但是如果您尝试其中任何一个而不显式关闭 oWbk,您会发现 oWbk 仍然存在为一个空壳,所有关于它的信息调用和请求 (try>Debug.Print> TypeName(oWbk)) 都将返回 'Automation Error' 消息。

上一个答案中的一些评论提到 UserForm 对象 - 与 Dictionary 和 Shell 对象不同 - 是具有可见用户界面的对象。但这个用户界面不是 Excel 用户界面中的持久新对象,如工作簿或工作表。

幸运的是,您创建的对象归您的 Excel 会话所有,您可以再次实例化一个对象变量,以获得相同的方法和属性框架,并再次控制该对象:

Set oWbk = Application.Workbooks(Application.Workbooks.Count)
...当然,假设您有某种方法可以确保您确定了正确的工作簿对象:但这根本不是您的问题。

这个答案的意义在于:不是在 Excel 会话的“自己”内存中创建的对象。

Set oApp = CreateObject("Excel.Application")
此语句将创建一个 Excel 对象,该对象与新的 Workbook 一样,具有用户界面(尽管您需要将 .Visible 属性设置为 True 才能看到它)和内存中的持久存在:再一次,该对象是超过它的对象变量,并且关闭变量不会破坏对象。

与新的 Workbook 不同,它完全由你来指挥:它本身就是一个 Excel 会话,它分配自己的内存 - oApp 在当前会话内存中的“足迹”只是指针和名称:接口(vTable、iDispatch 和所有具有指向在 VBA 中操作 Excel 会话的神秘行为的结构的命名方法)存在于这个新 Excel 会话分配的内存块中。

以下是 Office 2010 和旧版 Excel 中发生的情况:

使用Set oApp = Nothing 取消对象变量会使该会话启动并运行,我强烈建议您使会话可见,以便您可以手动关闭它!

手动关闭该 Excel 会话,而不显式关闭 oApp 对象变量,肯定会使 oApp 处于“空壳”状态,一个冷酷无头的幽灵在哀号 '自动化对象已与客户断开连接!' 在代码库的阴暗角落。

但是,在 Office 2013 及更高版本中,Set oApp = Nothing 完全执行您所期望的引用计数,并且会话关闭。尝试一下:

Private Sub Test()
Dim oApp As Excel.Application
Set oApp = New Excel.Application 'Set oApp = CreateObject("Excel.Application")
oApp.Visible = True
Set oApp = Nothing
End Sub
如果另一个对象变量有引用,它不会在Set oApp = Nothing 上关闭 - 这不是唯一增加引用计数器的实体:GUI 中的用户活动(尝试创建一个新工作簿并对其进行编辑)使会话保持正常和跑步。

为了您自己的乐趣,看看oApp.Quit 是否真的关闭了oApp 并将其设置为Nothing

当然,oApp.Quit 肯定会关闭会话...

...或者会吗?如果该会话中发生了一些事情 - 长时间计算或“模态”错误消息,您必须在应用程序对象响应任何其他输入之前从用户界面或您的 VBA - 然后 oApp.Quit 不会关闭会话。

我们不要去那里。在所有条件相同的情况下,oApp.Quit 肯定会在 2010 年及更早版本的 Excel 中关闭会话。

但在 Office 2013 中,从最后一个对象变量调用“退出”只会隐藏 UI:对象变量仍会响应您的代码 - 不需要活动工作簿的方法和属性仍可通过 oApp 访问 - 并且Excel.exe 的单独实例在任务管理器的进程选项卡中清晰可见。

同样,通过单击用户界面中的“关闭”按钮退出新会话会关闭会话的窗口,但是,如果您的代码中有一个引用此应用程序对象的对象变量,它仍然存在,在内存中,并且'oApp' 仍然可以获取属性和方法。

因此,引用计数器在当前版本的 Excel 中以两种方式工作:对象存在直到引用计数减少到零,并且最后一个剩余的对象变量将不会通过退出“断开”命令或 UI 操作。

尽管如此,您的会话并不“拥有”那个新的应用程序对象:如果您已经解除了最后一个对象变量并将其设置为 Nothing,并且还有其他东西使 neww 会话保持活动状态 - 用户活动,或者一些内部流程 - 没有什么像 Application.Workbooks() 或 Worksheets() 集合来识别其他 Excel 会话并实例化指向 Excel.Application 对象的特定实例的对象变量。

有一些方法可以使用 API 调用来获取特定会话,但它们并不像您希望的那样可靠。

...所以,总而言之,“第 2 部分”有很多内容。

【讨论】:

  • 感谢您的详细回答。我偶然发现了这篇文章,想用 VBA 代码或其他方法获取引用计数器,发现你的回答很有教育意义。要了解有关您提到的主题的更多信息,我发现其中可能包含从 Windows API 到低级实现的几本书,您是否会推荐任何一本书作为入门级书籍?我懂一些 C++,但不懂 C,这纯粹是为了好玩,不是为了工作。
  • @Nicholas - 我会推荐 Bullen & Bovey 的“专业 Excel 开发”作为最终作品。 Windows API 一章对 VBA 开发者来说是一个很好的介绍;但作为 C++ 开发人员,更深入的知识将在您自己的库中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-11-24
  • 2017-01-11
  • 2013-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多