Excel VBA: Overflow error from too many undestroyed objects? - oop

when performing optimization tasks on a large dataset I receive an overflow runtime error 6 from time to time (generally after 1 hour or 2). The error goes away when I restart my macro from where it stopped, i.e. launch the macro again from the point where the error occured. Could an overflow error be related to some issue of having created too many objects that are not destroyed properly after use?
Here's a (simplified version) of my container class, which is used destroyed (via Set ... = nothing) and reconstructed (via Set ... = New) thousands of times.
'CG_data_point custom collection class
Public data_points As Collection
Private Sub Class_Initialize()
Set data_points = New Collection
End Sub
Public Sub AddDataPoint(mydate as date, price as double)
Dim new_data_point As CG_data_point
Set new_data_point = New CG_data_point
new_data_point.EnterData mydate, price
data_points.Add new_data_point
Set new_data_point = Nothing 'I assume this one could also be skipped
End Sub
Public Sub RMSE(X as double) as double
...
End Sub
Private Sub Class_Terminate()
Dim data_point As CG_data_point
For Each data_point In data_points 'destruct each data point individually
Set data_point = Nothing
Next data_point
Set data_points = Nothing
End Sub
'Main module
dim global_container as CG_data_container
sub do_optimizations()
Do
set global_container= new CG_data_container
.... do something with the data, have in call to global function RMSE_UDF as a cell formula for Solver
set global_container= nothing
While (...)
end sub
'worksheet function
function RMSE_UDF(X as double)
global_container.RMSE(X)
end function
The container variable global_container has to be global because it must be callable from a worksheet UDF (RMSE_UDF); a worksheet formula cannot have an object as argument, as far as I know, like "=RMSE(MyContainer,...)". The minimization of Root Mean Squared Errors (RMSE) is carried out with Excel Solver.

I don't think that this is necessarily the cause of your error but it is worth fixing anyway.
Compare and contrast these two parts of your code:
From AddDataPoint
data_points.Add new_data_point
Set new_data_point = Nothing
Here we are adding an object referred to by the temporary variable new_data_point to the collection. We then set new_data_point to Nothing to remove the reference to the object that it used to refer to. Obviously the collection will still have a reference to this object
From Class_Terminate
For Each data_point In data_points 'destruct each data point individually
Set data_point = Nothing
Next data_point
Here we are reading each item in turn from the collection into a temporary variable called data_point. We then set data_point to Nothing to remove the reference to the object that it used to refer to. (Maybe not quite so) obviously, the collection will still have a reference to this object.
To remove every object from the collection try repeatedly removing the first object in the collection until the collection is empty:
Do Until (data_points.Count < 1)
data_points.Remove 1
Loop

In this case, an overflow results when you attempt an assignment that exceeds the limits of the assignment's target. Could your data set have something in it that exceeds the limit of a date or possibly a double? Those are the 2 types that I see getting passed in. Maybe there is a mismatch somewhere and a big double is getting passed in as a date. You can check for this kind of stuff with a subroutine that, for instance, checks the bounds of a date before trying to write it to the collection.
The other possibility is the size of the collection itself, since it is indexed by a long. That is a pretty big number though, you would have to exceed 2,147,483,647 records.

Related

How to make my module move an object using value set in UserForm/

I'm trying to automatize some processes I often do at work. Unfortunately I have no clue about programming so I've been struggling a lot with this.
I have an userform that has a textbox where you're supposed to type in how much you want the object to be moved on X axis. There are multiple objects and I can't wait to experiment how to move them all in proper direction, but the issue is, I can't even move one.
I created a variable, but I can't seem to be able to use it with Object.Move.
Right now VBA tells me to assign the values to an array, so I did that, but I can't put variables into an array apparently.
I've tried simply declaring the variable as public, calling it let's say for example "Value", and using Object.Move (Value), 0
or
Object.Move Value(), 0
Tried both, because I was not sure which one is correct.
After many trials I finally skipped most of the previous errors I was getting, and now I'm stuck at trying to set up an Array using a variable from the UserForm.
Here's the code inside Userform
VBA
Public Sub TextBox1_Initialize()
M = TextBox1
Unload M
End Sub
And here's the code inside the module
VBA
Public M As Integer
Sub Move()
Dim XY(M, 0) As Integer
Object.Move XY
End Sub

How to get an object out of a collection in VBa

I used to have the following working code
Set result = DecodeJson(MyRequest.responseText)
Dim keys() As String
keys = GetKeys(result.issues)
Now, my approach has changed and instead of having result being the object, I receive a Collection based upon Array of class objects as class member in VBA
Dim resultsFromQueries As Collection
Set resultsFromQueries = GetAllJSonObjects(searchString) ' this calls DecodeJson(MyRequest.responseText) as per the code snippet above
Dim i As Integer
For i = 0 To resultsFromQueries.Count
Dim keys() As String
Dim item As Object
Set item = resultsFromQueries.item(i + 1) ' I guess it's not 0 based?
keys = GetKeys(item.result.issues) 'KABOOM
The issue I have now is I keep getting the following exception
Run time error '424':
Object required
Checking in the watch window, item shows as type Variant/String and has the value of "[object Object]"
Do I need to cast it?
I had that problem just a few weeks ago, the problem was the following:
The collection was collected with the follwing code:Collection.Add(OBJECT)
This however leads to the addition of the value of the Object into the Collection and can be solved as simple as leaving the parathesis.Collection.add OBJECT
So my advice is to go into the GetAllJSonObjects(searchString) function and
search for the addition process and make sure that it is without parethesis.
Like this the Collection will contain Objects and not variants and your code with item(i+1) should work properly.
Good luck!

Is a VBA object destroyed when the variable is set to a new object?

I haven't found an answer specific to this question so hopefully someone can clear it up for me.
I understand the VBA Garbage Collector uses a reference count to determine if an object is not longer required, and to explicitly disassociate a variable (thereby decrementing the reference count) you use:
Set objectVariable = Nothing
Here is what I have in a spreadsheet I'm working on right now:
Declare Function GetObject Lib "ObjectCreator.dll" () As Object
Public MyObject as Object
Sub MyMethod()
Set MyObject = GetObject()
...do stuff with MyObject...
Set MyObject = GetObject()
...do stuff with my new MyObject...
Set MyObject = GetObject()
...do stuff with my even newer MyObject...
Set MyObject = Nothing
End Sub
My question is: Do all three of the created objects get destroyed by the GC or only the last one? i.e. Does the reference count of an object get decremented when its referencing variable is set to another object rather than being set to Nothing?
When you assign an object reference to a variable, the reference count goes up by one, and when the variable loses the reference by some other assignment, the object's reference count goes down by one. For example:
Dim a As Collection
Set a = new Collection 'The reference count of this new Collection object is 1
Set b = a 'The reference count of the Collection object is now 2
Set b = new Collection 'The reference count of the original collection has gone back down to 1 because b is no longer a reference to it, and this new Collection has a reference count of 1
Set a = Nothing 'The original collection no longer has any active references, so VBA safely GCs it.
Set b = Nothing 'The newer collection now no longer has any active references either, so VBA safely GCs it.
Now, in your case you're talking about an external DLL, which may manage its own memory or running state differently internally. But the way that VBA handles COM reference counts is the same.

VBA in Word 2013 with TableOfContents Object issue

I'm trying to update my Table of Contents in one of my macros after I edit a bunch of stuff. I'm running into an odd issue though. Below is the snippet I was running.
Note: user_document is declared globally as a Document object. It is accessible in this function.
Private Sub RunBuild_Click()
Dim TOC As TableOfContents
With user_document
For Each TOC In .TableOfContents 'ERROR OCCURS HERE
TOC.Update
Next
End With
user_document.Save
End Sub
When I run this, I get an object doesn't support this property or method error. I can't seem to figure out why I can't access the TableOfContents object in this document through a loop. The For Each block is supposed to enumerate the collection of objects so I can loop them, but it's acting like this concept doesn't exist. I've seen other posts for updating all Table of Contents that use this exact type of loop and have no issues.
If I use the line
user_document.TableOfContents(1).Update
The command runs perfectly fine. However, this doesn't allow me to catch the situation that no TOC exists or when multiple exists, it will only update the first instance.
Any ideas? I'm stumped. I keep rewriting the loop and trying different ways to access the object but they all throw the same error.
I've figured it out.
The TOC object I declared is of type "TableOfContents", but the Document Object contains a TablesOfContents object with individual TableOfContents objects inside.
The following code correctly loops through the structure.
Private Sub RunBuild_Click()
Dim TOC As TableOfContents
With user_document
For Each TOC In .TablesOfContents
TOC.Update
Next
End With
user_document.Save
End Sub
This also explains why i had trouble without getting a more meaningful error - I was accessing valid objects incorrectly instead of invalid objects correctly. Since word wasn't fussing about the object being invalid, I didn't even think to check the object for singular/plural naming.
Edit1: My Bad. Your code should work if you write it like this:
Dim TOC as TableOfContents ' without s on Table
With user_document
For Each TOC In .TablesOfContents ' with s on Table
TOC.Update
Next
End With
But below should work as well just fine.
Dim i As Long
With user_document
For i = .TablesOfContents.Count To 1 Step -1
.TablesOfContents(i).Update
Next
End With
This is assuming user_document is a document object properly set.

Not all controls in collection are being copied

I'm a bit confused here. I'm copying all the controls from one form to a panel on the main form and for some reason only about half of them copy.
Private Sub switchComponent()
Dim selection As String = TreeView1.SelectedNode.Text
Panel1.Controls.Clear()
Dim query = From cont In serverDic(selection).Controls
Select cont
For Each copier As Control In query
Panel1.Controls.Add(copier)
Next
End Sub
serverDic is defined as:
Dim serverDic As New Dictionary(Of String, frmServer)
When stepping through the code, serverDic(selection).Controls has 12 elements, but only 6 of them get copied. Next time this gets called, only 3 get copied. Does Panel1.Controls.clear() somehow kill the references?
EDIT: Just to show that there are infact 12 elements in the collection:
The problem here is that you are iterating over a collection that you are changing. When you add a Control to an container it is implicitly removed from it's previous parent and hence query. This is why you see exactly half of the items get removed.
With most collections this would be more apparent because they would throw if modified during an enumeration. The primary source of query here though is ControlCollection which does allow for modifications while enumerating.
To fix this problem just add the following line before the For Each loop.
query = query.ToList()