25

最近我遇到了一个让我很困惑的问题;它让我很忙,我在网上找不到透明的解释。
它与 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% 地知道我为什么要这样做。

4

2 回答 2

12

好问题 :)

Excel 控制其对象的创建。同样,它也控制着它们的毁灭。

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

每当您这样做时,将删除为其相关对象Set x = Nothing命名的引用(指针) 。x这并不意味着对象本身将从内存中删除。对象是否会从内存中删除,取决于各种因素。

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

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

它类似于Close在 VB6 中调用窗体。以vb6中的一个表格为例。

Dim f As Form
Set f = Form1
f.Show

'
'~~> Rest of the code
'

Set f = Nothing

这会破坏表格吗?:)

跟进

问题2呢?谢谢 – Kim Gysen 14 分钟前

在此处输入图像描述

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

于 2012-07-09T13:39:19.727 回答
2

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

这将涵盖三个关键点:

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

现在继续阅读...

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

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

在这两种情况下,都会分配内存,实例化对象变量(以及它们指向方法和属性的指针的 vTable),并且它们是你的命令,直到你关闭它们:

Set oDict = Nothing
Set oWShell = Nothing

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

但是这个对象是持久的:

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

您实际创建的是一个 Workbook对象- 一个带有活动工作表和完整用户界面的工作簿窗口 - 以及一个 Workbook对象变量- 程序员的 COM 接口,Workbook 对象的方法和属性表 - 您可以使用命名实体“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) ) 将返回“自动化错误”消息。

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

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

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

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

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

与新的工作簿不同,它不是完全由您来指挥:它本身就是一个 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 部分”中有很多内容。

于 2017-08-30T20:00:43.697 回答