What are the benefits of setting objects to "Nothing" - excel

I've noticed that some members of the Stack Overflow community will use Set Object = Nothing in closing procedures. I was able to find why this is useful for instances of Access, but no answer has been satisfying when it comes to doing this for Excel, so my question is
What are the benefits of setting objects to Nothing in VBA?
In the sample code below, is setting my objects ws and Test equal to Nothing a waste of space? Else, if doing so is in fact good practice, why?
Dim ws as Worksheet
Dim Test as Range
Set ws = Sheets(“Sheet1”)
Set Test = ws.Range(“A1”)
'Utilize Test variable (copy, paste, etc)
Set Test = Nothing
Set ws = Nothing
Exit Sub

If this was managed .NET code (which is garbage-collected), you'd have to Release every single COM object you ever accessed, lest the host process (EXCEL.EXE) would likely remain running in the background, consuming memory and unable to completely tear down.
But this is VBA code (which is reference-counted), moreover VBA code that uses objects that the host application controls - these objects will die when the host application shuts down, and when that happens the VBA execution context is long gone.
In other words, all these Set ... = Nothing instructions are completely redundant.
In some specific cases, when you're dealing with a 3rd-party API / type library, it's possible that objects don't entirely clean up. For example you might be creating an Access.Application instance, and find that a "ghost" ACCESS.EXE process remains open in Task Manager well after Excel exited: that's a sign that you're leaking an object reference somehow, somewhere, and Set ... = Nothing can help prevent that.
However I wouldn't recommend systematically nulling all object references like that. Only when not doing it causes a problem. And even then, it's going to be one or two objects dragging everything down, not all of them. If ACCESS.EXE shuts down properly, there's no reason to clutter up your code with such instructions.
Avoiding storing object references in global state helps, too. If everything is local, in theory all objects involved are destroyed as soon as the local scope exits.

Related

Are asynchronous callback functions safe to use in VBA?

Too long for a comment on the question that prompted this, so I'll ask here.
Is it safe (at least from the problems I talk about below) to use callback function pointers in VBA code; given its slightly unusual relationship to the host application and the quirks of the interpreter?
This question about using WinAPI to register callbacks discusses potential pitfalls which lead to crashing when the OS invokes the callback function. Specifically, it asks why interacting with the worksheet occasionally leads to problems in the TIMERPROC callback. My immediate thought upon reading the description was that the Excel Object Model doesn't work well with asynchronous programming.
The question includes an extract of code which "works", I won't include it here though because I think this answer to a related question actually illustrates it much more succinctly:
I was able to solve it with
On Error Resume Next
[...]
Sub TimerProc(ByVal HWnd As Long, ByVal uMsg As Long, ByVal nIDEvent As Long, ByVal dwTimer As Long)
On Error GoTo BeforeExit
Range("A1") = "test"
BeforeExit:
End Sub
...that confirmed my idea that the problem was probably coming from interacting with the object model (Range("A1") = "test") asynchronously whilst it was in some locked state, and could be solved by catching the error with OERN.
In that original question, #NigelHeffernan gave a great answer about what might be causing a crash when Windows invokes the callback function, which he chalks down to 3 reasons:
[developers using the API] are assumed to
have put the error-handling and contingency management in place in the
calling code.
Those assumptions are:
There will definitely be a valid function behind that pointer;
It definitely be available when it is called;
...And it will raise no errors to the caller.
Point 3 is (I think) most relevant to the specific question, and what I assume to be the cause of OP's crashes.
What I'm concerned about is that some of the comments (1,2) imply (to my mind at least) that point 2 might be to blame; that Excel being in "edit mode" somehow suspends VBA code from running and means the OS can't run the callback, but instead gets an "Excel is busy" error.
I know that VBA code often runs in the host's main/UI thread, which means that if Excel is busy running other UI stuff then VBA can't execute. However the message loop which reads the WM_TIMER message and ultimately runs the callback necessarily lives in the same thread as my VBA code; so the callback won't be called when Excel is actually busy processing other UI messages. Sure the Excel Object Model may be in some state that means trying to access it will raise errors, but then the issue is raising an error to the caller, not that the callback is unavailable.
Meanwhile another comment from the OP and some explanation given later on imply that their problem was actually the third factor (point 1 - the function pointer was invalid). What they've written seems to suggest that the TimerProc behind the function pointer is being Garbage-Collected and invalidated at some point during execution, OERN keeps it alive by holding a reference to it.
This whole idea seems really weird to me though; functions are not 1st class citizens in VBA and I don't think they follow COM reference counting (what would hold the reference to them anyway, a pointer is just a number not a reference?). I assumed that function pointers were set at compile-time, and they seem to remain valid even between End statements and reset buttons, so I'm not sure what would make them go out of scope as OP suggests. I also think that if they did it would have consequences for VBA code in general, not just when using APIs. So I'm not sure whether this is actually a problem to worry about.
TL;DR
When using callbacks in asynchronous single threaded code (such as with the SetTimer API), is there any risk of:
Callbacks falling out of scope between calls so that the function pointers provided to APIs become invalid
Excel/ the host application being in a "busy state" when the callbacks are invoked so that the calls fail
I can imagine why these might be problems across threads, but I thought the whole message queue system that Windows is built on would prevent this from being a problem for single threaded VBA code.
Or in other words, am I right in thinking that:
The host application should have no influence on the "availability" of VBA code on that thread
There is no reason for a function pointer to become invalid (un-callable) after I hit F5 - except perhaps for some manual memory mischief
Oh and please do read those comments in context, it's perfectly possible that I've totally misinterpreted them:) (I don't have much experience in this area at all)

Blue Prism Excel VBO Given key not in dictionary error

I have two processes that operate on a single Excel file.
The first process creates the Excel instance and opens/creates and activates a workbook and worksheet. The second process makes entries to the spreadsheet. The first process passes the Excel handle as an output parameter to the second process but, when the second process attempts to interact with the workbook, a "Given key not in dictionary" error occurs.
I speculate that the handle is just a means for a process to distinguish between Excel instances to which it is connected and the second process needs to connect to the Excel instance opened by the first process. The Excel VBO contains Attach and Attach Worksheet pages that may provide this functionality, but I cannot find any instructions or documentation for the Excel VBO. There may be more than one Excel instance open and I'm not sure how to refer to the correct instance.
Is my assumption that I need to connect the second process to the Excel instance opened by the first process correct? If so, how do I do this? If not, can anyone tell me what causes the dictionary error and how I can address it?
handles are kept track of internally in memory by a particular instance of the MS Excel VBO. They are not shared between instances of the VBO.
Given the above, and assuming your code is set up exactly as you've described (two distinct processes), this is expected behavior: the instance of the MS Excel VBO that holds the handle for the instance of Excel you're attempting to interact with is purged from memory at the end of the process.
Regarding the "Attach" functionality and associated documentation: most all out-of-the-box VBOs do have documentation available, and is always accessible by clicking the "i" button as emphasized in my screenshot below:
Clicking this pops an Internet Explorer window with the documentation for the particular object you have set in the "Business Object" field of this window. In this case, the MS Excel VBO action "Attach" has the following description:
1.3 Attach
Back-compatible link to 'Open Instance'. This opens the first running
instance of Excel found and links to it in this object. Returns:
- handle : Number : An integer with which the instance opened can be
identified.
- Enable Events : Flag : Indicates that events should be
enabled / disabled on the attached instance - defaulted to True
In your particular use case, this may be a viable action. In most cases/designs (esp. including considerations for resiliency), it should be considered that the automated solution may inadvertently attach to another Excel instance (if one is present). As such, you might want to consider re-factoring your process design to create and interact with the Excel instance within the same Blue Prism process. If you need logical separation of the code that launches Excel & handles the processing, you might consider using individual Pages and page references as opposed to separate processes altogether.
The last point above lends nicely to your assumption regarding the use of handle. At the risk of being redundant: your assumption itself is correct, but you might want to consider a slight re-design to your processes. It's unlikely that the optimal design of a given Blue Prism process would open an instance of Excel in one process, and not interact with it until another process.

Atomicity in Excel VBA

I have the following code in a Form Module in an xlsm:
Public Sub DoModal() ' Note: Called by LaunchHelper which is associated with F12 in Workbook_Open
Dim ewsActive As Worksheet: Set ewsActive = ThisWorkbook.ActiveSheet
If ewsActive Is Overview Then ' Overview is the CodeName of one of the Worksheets
Set ewsOverview = ewsActive ' This is a Private variable in the Form Module
ShowSafe
End If
End Sub
Private Sub ShowSafe() ' Calling Show directly caused random crashes, I concluded that these were caused by that the Form was not loaded into memory, this function makes sure, it is in memory before calling Show
Dim blnLoaded As Boolean: blnLoaded = False
Dim objForm As Object: For Each objForm In UserForms
If objForm.name = Me.name Then
blnLoaded = True
Exit For
End If
Next objForm
If blnLoaded = False Then
Load Me
End If
Show
End Sub
As far as I know:
VBA is single-threaded, it can never happen that two Subs or Functions are executed in parallel (e.g. while processing Worksheet_Activate, Worksheet_SelectionChange is also called, and the order of executing the statements inside those two functions is undetermined).
However, if we look at Excel+VBA as a system, then it is multi-threaded because Excel and VBA run in parallel (and Excel is multi-threaded itself, in addition).
My problem is:
I see a race condition in DoModal, because checking and acting is not atomic, and the following scenario is possible:
(1) The condition ewsActive Is Overview is evaluated as true, so we start executing the branch inside the If block.
(2) The execution is taken over by Excel, where the user switches to a different Worksheet.
(3) ShowSafe is called while we the ActiveSheet is not Overview but something else.
Do you agree with my reasoning? Is it correct that in spite of the checking, the Form might show up on a different Worksheet? So far, I have not succeded in making this error happen but I would like to know if it was possible in theory, even if it has only a low chance.
Where can I find more information about problems and guarantees related to multi-threading in VBA?
For the user to switch the ActiveSheet, Excel needs to process User Events. Experienced VBA programmers usually let Excel process user events by explicitly calling DoEvents, because Excel's GUI freezes/starves for CPU while VBA code is running. This suggests that the race condition you describe is extremely unlikely, if possible at all.
However, this non-concurrency between Excel's GUI and its VBA runtime is only know by practice. I didn't find official documentation that formally confirms that there's absolutely zero chance for Excel to process user events while VBA code is running (without explicitly yielding). We only know by practice that this chance is "almost zero".
For this reason I would say that your suggested race condition is, though extremely unlikely, theoretically possible until we have an official statement or documentation that rules out that concurrency.
p.s. I consider this question as of academic interest only, and the code of illustrative purpose only, because obviously there no relevance for a form to "check" whether it is actually loaded.
I'm not seeing what you're trying to do. Why does it matter what sheet is active when you show your user form? If it needs to be Worksheets("overview") then just Activate it on form Load event? It won't hurt if it's already active.
Similarly if you aren't sure if it's loaded then call Load just before your Show
Finally you're referring to the user form as Me - which implies that you're running this from the user form itself. So this code will not run unless the form is loaded into memory, so ShowSafe is pointless code.

Macros causing Lag

I am running a program that uses 5 macros and lots of formulas. Some of the macros I have asked for your help on here. After putting the program together there is alot of lag. I mean if we delete a line we have to wait 1 to 2 minutes for it do the that process. Any ideas on what I should be looking at? I know this sounds somewhat vague but I am at a loss of where to starting looking to resolve the lag. Do I look at computer, server, or program?
From Visual Basic Concepts (part of Help)
You can make your Visual Basic applications run faster by optimizing the way Visual Basic resolves object references. The speed with which Visual Basic handles object references can be affected by:
Whether or not the ActiveX component has been implemented as an in-process server or an out-of-process server.
Whether an object reference is early-bound or late-bound.
In general, if a component has been implemented as part of an executable file (.exe file), it is an out-of-process server and runs in its own process. If it has been implemented as a dynamic-link library, it is an in-process server and runs in the same process as the client application.
Applications that use in-process servers usually run faster than those that use out-of-process servers because the application doesn't have to cross process boundaries to use an object's properties, methods, and events. For more information about in-process and out-of-process servers, see "In-Process and Out-of-Process Servers."
Object references are early-bound if they use object variables declared as variables of a specific class. Object references are late-bound if they use object variables declared as variables of the generic Object class. Object references that use early-bound variables usually run faster than those that use late-bound variables.
See this link from a Microsoft person. This is excel specific rather than VBA. Autocalc and other calc options/screenupdating etc.
http://blogs.office.com/2009/03/12/excel-vba-performance-coding-best-practices/
Minimise Dots
So if you are interested in performance minimise dots (each dot is a lookup), especially in loops.
There are two ways. One is to set objects to the lowest object if you are going to access more than once.
eg (slower)
set xlapp = CreateObject("Excel.Application")
msgbox xlapp.worksheets(0).name
(faster because you omitt a dot every time you use the object)
set xlapp = CreateObject("Excel.Application")
set wsheet = xlapp.worksheets(0)
msgbox wsheet.name
The second way is with statement. You can only have one with active at a time.
This skips 100 lookups.
with wsheet
For x = 1 to 100
msgbox .name
Next
end with
String Concatination
And don't join strings one character at a time. See this from a VBScript programmer. It requires 50,000 bytes and many allocation and deallocation to make a 100 character string.
http://blogs.msdn.com/b/ericlippert/archive/2003/10/20/53248.aspx

Core Data: awakeFromFetch Not Getting Called For Unsaved Contexts

First, let me illustrate the steps to reproduce the 'bug'.
Create a new NSManagedObject.
Fault the managed object using refreshObject:mergeChanges:NO - At this time, the didTurnIntoFault notification is received by the object.
'Unfault' the object again by using willAccessValueForKey:nil - At this time, the awakeFromFetch notification is supposed to be received BUT NO NOTIFICATION COMES. All code relying of it firing fails, and the bread in the toaster burns :)
The interesting thing is that if I 'save' the managed object context before performing step 2, everything works okay and the awakeFromFetch notification comes as expected.
Currently the workaround that I am using is 'saving' the context at regular intervals, but that is more of a hack since we actually need to save the context once (when the application terminates).
Googling has so far returned nothing concrete, except a gentleman here that seems to have run into the same problem.
So my question is twofold - Is this really a bug, and if it is, then what other walkarounds (sic) do you suggest.
EDIT: THIS IS NOT A BUG BUT THAT WAS JUST ME BEING STUPID. See, if I turn an object to fault without saving it, then there is no history of the object to maintain. So in this case (i.e for an unsaved object) there is no logical concept of awakeFromFetch (since it was never saved). Please do let me know if I am still getting it all mixed up.
Anyways, turns out my 'actual' problem was somewhere else - hidden well behind 2 gotcha's
If you use refreshObject:mergeChanges:NO to turn an object to fault in order to break any retain cycles that core data might have established, you have to do the same for the child objects also - Each child object which might have gotten involved in a cyclic retain with someone else will have to be manually faulted. What I had (wrongly) assumed was that faulting the parent will automatically break the cycles amongst the children.
The reverseTransform function of your custom transformers will NOT be called when such a object (i.e. which has been forcefully faulted) is resurrected by firing a fault on it. This in my eyes IS a bug, since there is no other way for me to know when the object is alive again. Anyways, the workaround in this case was to set the staleness interval to an arbitrarily low value so that core data skips its cache and always calls the reverseTransform function to resurrect the object. Better suggestions are welcome.
it really has been one of those days :)

Resources