简体   繁体   English

VBA引用计数 - 对象销毁

[英]VBA Reference counting - Object destruction

Lately I've bumped into a question that made me pounder; 最近我碰到了一个让我更健康的问题; it kept me busy and I couldn't find a transparent explanation for it on the net. 它让我忙碌,我无法在网上找到透明的解释。
It is related to the destruction of Excel objects (which I use all the time and never really questioned before). 它与Excel对象的破坏有关(我一直使用它,以前从未真正质疑过)。

Background leading to my question: 背景导致我的问题:
With regular objects, you can instantiate an object using the keywords SET and NEW. 使用常规对象,您可以使用关键字SET和NEW来实例化对象。 For example: 例如:

Set classInstance = New className

Whenever we instantiate this way, the object is created in the heap memory and the reference counter is increased by 1. 每当我们以这种方式实例化时,对象就会在堆内存中创建,并且引用计数器会增加1。
In case I don't add more references, the following statement would bring the reference count back to zero: 如果我不添加更多引用,以下语句将引用计数返回到零:

Set classInstance = Nothing 

When the reference count goes to 0, the object is destroyed and cleared from memory and the "classInstance" points to . 当引用计数变为0时,对象将被销毁并从内存中清除,并且“classInstance”指向。

What I've read: 我读过的内容:
When we use the "CREATEOBJECT" function, it returns a reference to a COM object. 当我们使用“CREATEOBJECT”函数时,它返回对COM对象的引用。

Set oApp = CreateObject("Excel.Application")

Even though we could say: 即使我们可以说:

Set oApp = nothing 

The objects' reference count will go to 0, and oApp will not point to the object anymore. 对象的引用计数将变为0,oApp将不再指向该对象。

My questions : 我的问题
1) Why is it that this type of object requires to call the method .Quit before the object is actually being removed from memory? 1)为什么这种类型的对象需要在实际从内存中删除对象之前调用方法.Quit?
The same goes when adding a reference to a workbook object (workbooks.add or workbook.open) which requires the .close method. 添加对需要.close方法的工作簿对象(workbooks.add或workbook.open)的引用时也是如此。 Why can't these objects be automatically destroyed when bringing the reference count to zero? 为什么在将引用计数设置为零时不能自动销毁这些对象?
Which is the case when we say for example: 当我们说例如:

set oRange = nothing 

2) And is there a need to say: 2)有必要说:

oApp.Quit
set oApp = nothing 

Since the Application object is already cleared from memory when applying .Quit, there is no object to be released anymore. 由于在应用.Quit时已经从内存中清除了Application对象,因此不再有对象被释放。
The only reason I could come up with, why oApp would be set to Nothing after Quit, would be because it could be pointing to an unused memory location (on the heap) and could lead to confusion later if this memory would be re-assigned (although in VBA I find this hard to imagine). 我想出的唯一原因是,为什么oApp在Quit之后会被设置为Nothing,因为它可能指向一个未使用的内存位置(在堆上),如果重新分配这个内存,可能会导致混乱(虽然在VBA中我发现这很难想象)。 I was questioning myself if this conclusion is correct and I would like to receive confirmation for that from someone who knows the answer. 我问自己这个结论是否正确,我想从知道答案的人那里得到确认。
Please, tell me if I see this wrongly. 请告诉我,如果我看错了。

3) What they call in VBA "a reference to an object" (such as oApp in the code above), I see them as pointer variables in C. Would it be safe to use this statement or again, am I seeing this wrongly? 3)他们在VBA中称之为“对象的引用”(例如上面代码中的oApp),我将它们视为C中的指针变量。使用此语句是否安全或再次使用,我是否错误地看到了这一点?

Generally is not hard to apply .Quit and set to nothing, but it would be nice to receive some accurate information on the topic. 一般来说并不难应用。确定并设置为空,但收到关于该主题的一些准确信息会很好。 So that I know for 100% percent why I am doing it. 所以我知道为什么我这样做的百分之百。

Good Question :) 好问题 :)

Excel controls the creation of its objects. Excel控制其对象的创建。 Likewise it also controls their destruction. 同样,它也控制着它们的破坏。

Setting oApp = Nothing just destroys the object reference. 设置oApp = Nothing只会破坏对象引用。 It doesn't remove the Application. 它不会删除应用程序。 To destroy an Excel object, you have to use it's .Quit method. 要销毁Excel对象,必须使用它的.Quit方法。

Whenever you do, Set x = Nothing , the reference(pointer) named x to its relevant object is removed. 无论何时, Set x = Nothing ,都会删除名为x的引用(指针)到其相关对象。 This doesn't mean that the object itself will be removed from the memory. 这并不意味着对象本身将从内存中删除。 Whether the object will be removed from memory or not, depends on various factors. 对象是否将从内存中删除,取决于各种因素。

  1. Whether there are more references pointing towards the same object. 是否有更多引用指向同一个对象。 If there are, the object will not be removed. 如果有,则不会删除该对象。 The reference count must be zero. 引用计数必须为零。
  2. The internal implementation of the destructor of that object. 该对象的析构函数的内部实现。

The .Quit method is defined to graciously remove all the memory objects excel has allocated, and close itself. .Quit方法被定义为优雅地删除excel已分配的所有内存对象,并自行关闭。

It is similar to calling Close on a form in VB6. 它类似于在VB6中调用表单上的Close Take for example, a form in vb6. 例如,vb6中的表单。

Dim f As Form
Set f = Form1
f.Show

'
'~~> Rest of the code
'

Set f = Nothing

Will this destroy the form? 这会破坏形式吗? :) :)

FOLLOWUP 跟进

How about question 2? 问题2怎么样? Thanks – Kim Gysen 14 mins ago 谢谢 - 金吉森14分钟前

在此输入图像描述

It might not be exactly as shown here, and compiler optimizations may make things behave differently... but this is the basic concept that is at work. 它可能不完全如此处所示,并且编译器优化可能使事情表现不同......但这是工作中的基本概念。

Part 2 of your question is quite interesting, and it's well worth an extended answer. 你问题的第2部分非常有趣,值得一个扩展的答案。

This is going to cover three key points: 这将涉及三个要点:

  • Objects and object variables; 对象和对象变量;
  • Pitfalls when dismissing objects; 解雇对象时的陷阱;
  • ...And an important change in reference-counting the Application object in Excel 2013. ...以及在Excel 2013中计算Application对象的引用的重要更改。
But, if you want a short answer, it's: "Not all objects are equal". 但是,如果你想要一个简短的答案,那就是: “并非所有对象都是平等的”。

Now read on... 现在继续阅读......

Some objects are created in your Excel session's 'own' memory space, and their memory allocation is controlled by your session; 某些对象是在Excel会话的“自己的”内存空间中创建的,其内存分配由您的会话控制; some objects have persistent components that exist after the object variable is dismissed; 某些对象具有在解除对象变量后存在的持久性组件; and some do not: 有些人不这样做:

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

In both these cases, memory is allocated, the object variables (and their vTable of pointers to methods and properties) are instantiated, and they are yours to command until you dismiss them: 在这两种情况下,都会分配内存,对象变量(以及它们对方法和属性的指针的vTable)是实例化的,在你解雇它们之前它们是你的命令:

 Set oDict = Nothing Set oWShell = Nothing 

And, on dismissal, no trace of them remains. 而且,在解雇时,没有遗留下来。

But this object is persistent: 但是这个对象是持久的:

 Dim oWbk as Excel.Workbook 
Set oWbk = Application.Workbooks.Add
...You've created a new workbook object and, if you dismiss the object variable with Set oWbk = Nothing , you will see that the new workbook object still exists as a visible presence in the user interface. ...您已经创建了一个新的工作簿对象,如果您使用Set oWbk = Nothing关闭对象变量,您将看到新的工作簿对象仍然作为用户界面中的可见存在而存在。

What you actually created was a Workbook object - a workbook window with an active worksheet and the full user interface that goes with that - and a Workbook object variable - a programmer's COM interface, a table of methods and properties for the Workbook object - that you can manipulate in code using the named entity 'oWbk'. 你实际创建的是一个Workbook 对象 - 一个带有活动工作表的工作簿窗口和一个完整的用户界面 - 以及一个Workbook 对象变量 - 一个程序员的COM接口,一个工作簿对象的方法和属性表 - 你可以使用命名实体'oWbk'在代码中操作。

Dismissing the oWbk object variable removes that framework, but the Workbook itself will still exist: you've created a Workbook object, and it's yours to keep. 解除oWbk对象变量会删除该框架,但是工作簿本身仍然存在:您已经创建了一个Workbook对象,并且它是您自己保留的。

The object is more than its object variable and dismissing the variable does not destroy the object: it just dismisses an interface, a framework of methods and properties that you can use to manipulate the object in code. 该对象不仅仅是它的对象变量,并且忽略变量不会破坏对象:它只是解除了一个接口,一个方法和属性框架,可以用来操作代码中的对象。

Closing the Workbook, with or without saving a file, should automatically dismiss the object variable and clear up the memory allocated for that interface of properties, methods and attributes: 无论是否保存文件,关闭工作簿都应自动关闭对象变量并清除为该属性,方法和属性接口分配的内存:

 'try this: oWbk.Close SaveChanges:=False ' or maybe this: Application.Workbooks(Application.Workbooks.Count).Close SaveChanges:=False 
...That is to say, you would expect both of those commands to call Set oWbk= Nothing - especially the oWbk.Close command - but if you try either of them without explicitly dismissing oWbk, you will find that oWbk still exists as an empty husk, and all calls and requests for information on it (try> Debug.Print> TypeName(oWbk) ) will return 'Automation Error' messages. ...也就是说,你会期望这两个命令都调用Set oWbk= Nothing - 尤其是oWbk.Close命令 - 但是如果你在没有明确解雇oWbk的情况下尝试其中任何一个,你会发现oWbk仍然存在空壳,所有调用和请求信息(尝试> Debug.Print> TypeName(oWbk) )将返回'自动错误'消息。

Some of the commments in the previous answer mention the UserForm object which - unlike the Dictionary and the Shell object - is an object with a visible user interface. 上一个答案中的一些提交提到了UserForm对象,它与Dictionary和Shell对象不同,是一个具有可见用户界面的对象。 But this user interface is not a persistent new object in the Excel user interface like a Workbook or a worksheet. 但是,此用户界面不是Excel用户界面中的持久性新对象,如工作簿或工作表。

Luckily for you, the object you created is owned by your Excel session, and you can instantiate an object variable again, to get the same framework of methods and properties, and take control of the object again: 幸运的是,您创建的对象由您的Excel会话拥有,您可以再次实例化一个对象变量,以获得相同的方法和属性框架,并再次控制该对象:

 Set oWbk = Application.Workbooks(Application.Workbooks.Count) 
...Assuming, of course, that you have some way of being sure that you identified the right workbook object: but that's not your question at all. ...当然,假设您有某种方法可以确定您确定了正确的工作簿对象:但这根本不是您的问题。

Where this answer is going is: objects that are not created in your Excel session's 'own' memory. 答案的回答是:在Excel会话的“自己的”内存中未创建的对象。

 Set oApp = CreateObject("Excel.Application") 
This statement will create an Excel object which, like the new Workbook, has a User Interface (although you'll need to set the .Visible property True to see it) and and a persistent presence in memory: once again, the object is more than its object variable, and dismising the variable does not destroy the object. 这个语句将创建一个Excel对象,就像新的工作簿一样,它有一个用户界面(虽然你需要将.Visible属性设置为True才能看到它)并且在内存中持久存在:再一次,对象更多比它的对象变量,并且推断变量不会破坏对象。

Unlike the new Workbook, it isn't quite yours to command: it's an Excel session in it's own right, it allocates its own memory - oApp's 'footprint' in your current session's memory is just the pointer and the name: the interface (vTable, iDispatch, and all those named methods with pointers to the structures that implement the arcane act of manipulating an Excel session in VBA) exists in the block of memory allocated by this new Excel session. 不同的是新的工作簿,这是不太你的指挥:它是在它自己的权利的Excel的会话,分配其自己的内存- oApp的“足迹”在当前会话的内存仅仅是指针和名称:接口(VTABLE ,iDispatch,以及所有那些带有指向实现在VBA中操作Excel会话的神秘行为结构的指针的命名方法)存在于由这个新的Excel会话分配的内存块中。

Here's what happens in Office 2010, and older versions of Excel: 以下是Office 2010和旧版Excel中发生的情况:

Dismissing the object variable with Set oApp = Nothing leaves that session up and running, and I would strongly suggest that you make the session visible so that you can close it manually! 使用Set oApp = Nothing解除对象变量会使该会话启动并运行,我强烈建议您使会话可见,以便您可以手动关闭它!

Closing that Excel session manually, without explicitly dismissing the oApp object variable, will definitely leave oApp in the 'empty husk' state, and a grim and headless spectre wailing 'The Automation object has disconnected from its clients!' 手动关闭该Excel会话,而不明确地忽略oApp对象变量, 肯定会使oApp处于“空壳”状态,而且一个严峻且无头的幽灵嚎叫着“自动化对象已与客户端断开连接!” in the dark corners of your code base. 在代码库的黑暗角落。

But, in Office 2013 and later versions, Set oApp = Nothing performs exactly the reference-counting you would expect and the session closes. 但是,在Office 2013及更高版本中, Set oApp = Nothing完全执行您期望的引用计数并且会话关闭。 Try it: 试试吧:

 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
It won't close on Set oApp = Nothing if another object variable has a reference - and that's not the only entity that gets to increment the reference counter: user activity in the GUI (try creating a new workbook and editing it) keeps the session up and running, too. 如果另一个对象变量有引用,它将不会关闭Set oApp = Nothing - 并且这不是增加引用计数器的唯一实体:GUI中的用户活动(尝试创建新工作簿并编辑它)会保持会话也正在运行。

For your own amusement, see if oApp.Quit does actually dismiss oApp and sets it to Nothing . 为了您自己的娱乐,请查看oApp.Quit是否确实解除了oApp并将其设置为Nothing

Of course, oApp.Quit will definitely close the session... 当然, oApp.Quit一定会关闭会议......

...Or will it? ......还是会吗? If there is something going on in that session - a long calculation, or an 'modal' error message that you have to see and click before the Application object responds to any other input, from the user interface or your VBA - then oApp.Quit won't close the session. 如果该会话中发生了某些事情 - 在Application对象响应任何其他输入之前,您需要查看并单击的长计算或'模态'错误消息,请从用户界面或您的VBA - 然后oApp.Quit不会关闭会话。

Lets not go there. 让我们不去那里。 All things being equal, oApp.Quit will definitely close the session in 2010 and earlier versions of Excel. 在所有条件相同的情况下, oApp.Quit肯定会关闭2010年的会话和早期版本的Excel。

But in Office 2013, calling 'Quit' from the last object variable merely hides the UI: the object variable still responds to your code - the methods and properties that don't require an active workbook are still accessible via oApp - and a separate instance of Excel.exe is clearly visible in the Processes tab of Task manager. 但是在Office 2013中,从最后一个对象变量调用“Quit”只会隐藏UI:对象变量仍然响应您的代码 - 仍然可以通过oApp访问不需要活动工作簿的方法和属性 - 以及一个单独的实例在任务管理器的“进程”选项卡中清晰可见Excel.exe。

Likewise, quitting the new session by clicking the 'close' button in the user interface closes the session's windows but, if there's an object variable with a reference to this application object in your code, it's still there, in memory, and 'oApp' can still get at the properties and methods. 同样,通过单击用户界面中的“关闭”按钮退出新会话将关闭会话的窗口,但是,如果在代码中有一个对象变量引用此应用程序对象,它仍然存在于内存中,并且“oApp”仍然可以获得属性和方法。

So the reference counter works both ways in current versions of Excel: the object exists until the reference count decrements to zero, and the last remaining object variable will not be left 'disconnected' by a quit command or UI action. 因此,引用计数器在当前版本的Excel中以两种方式工作:对象一直存在,直到引用计数减少到零,并且最后剩下的对象变量不会被退出命令或UI操作“断开”。

Nevertheless, your session doesn't 'own' that new application object: if you've dismissed the last object variable and set it to Nothing , and there's something else keeping the neww session alive - user activity, or some internal process - there's nothing like the Application.Workbooks() or Worksheets() collection to identify other Excel sessions and instantiate an object variable pointing to a specific instance of an Excel.Application object. 然而,你的会话并不“拥有”那个新的应用程序对象:如果你已经解除了最后一个对象变量并将其设置为Nothing ,并且还有其他东西让新会话保持活动 - 用户活动或某个内部进程 - 那就什么都没有了与Application.Workbooks()或Worksheets()集合一样,用于标识其他Excel会话并实例化指向Excel.Application对象的特定实例的对象变量。

There are ways of getting a specific session using API calls, but they aren't as reliable as you might wish them to be. 有一些方法可以使用API​​调用来获取特定会话,但它们并不像您希望的那样可靠。

...So, all in all, there's quite a lot in that 'part 2'. ......总而言之,“第2部分”中有相当多的内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM