VBA keeps running after end of routine causing application crash, found an issue with Class Modules

K

keeena

I finally figured out a problem I've been having and was wondering if
this is a known issue. For this discussion I will assume the host app
is Excel.

My problem was this: Code would run fine if run beginning to end w/o
breakpoints. However, if I...

a. put a breakpoint in
b. open the Locals Window
c. viewed (expanded) an expression which is an instance of my own class
d. and then continued code execution

....the code would complete but the VBA environment would still be
running (the VBA application title bar would still say "[running]").
You can stop execution via the Reset button, but application (e.g.
Excel) will hang when you attempt to close it.

The reason this was happening:

The code has 2 object classes and 1 collection class: Car -> Parts
Collection -> Parts. The Parts object class has a Class_Terminate with
no code inside of it. This empty terminate causes the problem. The
code to reproduce this is below. You must be familiar with creating
custom collection classes by uncommenting some lines of code in a text
editor and then importing the file into VBA.

So, can anyone tell me if this is a known issue? I never knew about it
and it caused me significant grief when attempting to do other
debugging. If anything I hope it helps anyone else who may hit this
issue.

-K

'CODE SAMPLE
'ANYTHING IN FULL CAPS IS INTENTED TO BE A COMMENT

'STANDARD MODULE - BEGIN
Option Explicit
Sub Test()
Dim c As cCar
Dim p As cPart
Set c = New cCar
With c '<- PUT THE BREAKPOINT HERE WHEN PERFORMING THE
TEST

' WHEN THE CODE EXECUTION HITS THE BREAKPOINT:
' - OPEN THE LOCALS WINDOW
' - EXPAND THE ExPRESSION "c"
' - CLOSE THE LOCALS WINDOW
' - PROCEED WITH CODE EXECUTION
' - CODE WILL COMPLETE, BUT VBA EDITOR WILL STILL BE RUNNING (CHECK
TITLE BAR)

' YOU CAN STOP EXECUTION WITH THE RESET BUTTON. CLOSE VBA AND THEN
ATTEMPT TO CLOSE
' EXCEL - IT WILL CRASH THE APP HOWEVER.

.Make = "Acura"
.Model = "TL Type S"
End With
Set p = c.Parts.Add("Tire")
Set p = c.Parts.Add("Rim")
MsgBox c.Make & " " & c.Model
For Each p In c.Parts
MsgBox p.Name
Next p
Set p = Nothing
Set c = Nothing
Msgbox "Code done"
End Sub
'STANDARD MODULE - END


'CLASS MODULE "cCAR" - BEGIN
Option Explicit
Private mParts As cParts
Private mMake As String
Private mModel As String
Private Sub Class_Initialize()
Set mParts = New cParts
End Sub
Property Get Parts() As cParts
Set Parts = mParts
End Property
Property Let Parts(ByVal Value As cParts)
Set mParts = Value
End Property
Property Get Make() As String
Make = mMake
End Property
Property Let Make(ByVal Value As String)
mMake = Value
End Property
Property Get Model() As String
Model = mModel
End Property
Property Let Model(ByVal Value As String)
mModel = Value
End Property
'CLASS MODULE "cCAR" - END


'CLASS MODULE "cPARTS" - BEGIN
Option Explicit
Private mParts As Collection
Private Sub Class_Initialize()
'Initialize stuff here
Set mParts = New Collection
End Sub
Property Get Item(ByVal Index As Variant) As cPart
'Attribute Item.VB_UserMemId = 0
'This property has a hidden attribute to allow
'this class to iterate through members. Uncomment
'this attribute line in a text editor and then import
'back into VBA
On Error Resume Next
Set Item = mParts.Item(Index)
End Property
Property Get NewEnum() As IUnknown
'Attribute NewEnum.VB_UserMemId = -4
'Attribute NewEnum.VB_MemberFlags = "40"
'This property has 2 hidden attributes to allow
'the class to iterate through members. Uncomment
'these attributes in a text editor and then import
'back into VBA
Set NewEnum = mParts.[_NewEnum]
End Property
Property Get Count() As Long
Count = mParts.Count
End Property
Function Add(ByVal Value As String) As cPart
Dim p As cPart
Set p = New cPart
p.Init Value
mParts.Add p, Value
Set Add = p
End Function
Function Remove(ByVal Index As Variant) As Boolean
On Error Resume Next
mParts.Remove Index
Remove = (Err.Number = 0)
End Function
'CLASS MODULE "cPARTS" - END


'CLASS MODULE "cPART" - BEGIN
Option Explicit
Private Const mClassName As String = "PartClass"
Private mName As String
Private mDescription As String
Private Sub Class_Initialize()
mName = ""
mDescription = ""
End Sub
Private Sub Class_Terminate()
'Debug.print "Terminating cPart class" ' <- THIS EMPTY
CLASS_TERMINATE IS THE PROBLEM
End Sub
Friend Function Init(ByVal Value As String) As Boolean
mName = Value
Init = True
End Function
Property Get Name() As String
Name = mName
End Property
Property Let Name(ByVal Value As String)
mName = Value
End Property
Property Get Description() As String
Description = mDescription
End Property
Property Let Description(ByVal Value As String)
mDescription = Value
End Property
'CLASS MODULE "cPART" - END
 
G

George Nicholson

Unless I'm missing something, "Set p = Nothing" only terminates 1 'part' out
of 2, whichever 'p' that was in the last loop iteration. You still have a
2nd part in memory (even if you've destroyed the collection, the part is
still there.) Consider adding a Terminate event to cParts: just before the
collection is destroyed, loop and destroy the collection members.

That said, I'm not sure if this directly related to your reported problem.

HTH,
--
George Nicholson

Remove 'Junk' from return address.


keeena said:
I finally figured out a problem I've been having and was wondering if
this is a known issue. For this discussion I will assume the host app
is Excel.

My problem was this: Code would run fine if run beginning to end w/o
breakpoints. However, if I...

a. put a breakpoint in
b. open the Locals Window
c. viewed (expanded) an expression which is an instance of my own class
d. and then continued code execution

...the code would complete but the VBA environment would still be
running (the VBA application title bar would still say "[running]").
You can stop execution via the Reset button, but application (e.g.
Excel) will hang when you attempt to close it.

The reason this was happening:

The code has 2 object classes and 1 collection class: Car -> Parts
Collection -> Parts. The Parts object class has a Class_Terminate with
no code inside of it. This empty terminate causes the problem. The
code to reproduce this is below. You must be familiar with creating
custom collection classes by uncommenting some lines of code in a text
editor and then importing the file into VBA.

So, can anyone tell me if this is a known issue? I never knew about it
and it caused me significant grief when attempting to do other
debugging. If anything I hope it helps anyone else who may hit this
issue.

-K

'CODE SAMPLE
'ANYTHING IN FULL CAPS IS INTENTED TO BE A COMMENT

'STANDARD MODULE - BEGIN
Option Explicit
Sub Test()
Dim c As cCar
Dim p As cPart
Set c = New cCar
With c '<- PUT THE BREAKPOINT HERE WHEN PERFORMING THE
TEST

' WHEN THE CODE EXECUTION HITS THE BREAKPOINT:
' - OPEN THE LOCALS WINDOW
' - EXPAND THE ExPRESSION "c"
' - CLOSE THE LOCALS WINDOW
' - PROCEED WITH CODE EXECUTION
' - CODE WILL COMPLETE, BUT VBA EDITOR WILL STILL BE RUNNING (CHECK
TITLE BAR)

' YOU CAN STOP EXECUTION WITH THE RESET BUTTON. CLOSE VBA AND THEN
ATTEMPT TO CLOSE
' EXCEL - IT WILL CRASH THE APP HOWEVER.

.Make = "Acura"
.Model = "TL Type S"
End With
Set p = c.Parts.Add("Tire")
Set p = c.Parts.Add("Rim")
MsgBox c.Make & " " & c.Model
For Each p In c.Parts
MsgBox p.Name
Next p
Set p = Nothing
Set c = Nothing
Msgbox "Code done"
End Sub
'STANDARD MODULE - END


'CLASS MODULE "cCAR" - BEGIN
Option Explicit
Private mParts As cParts
Private mMake As String
Private mModel As String
Private Sub Class_Initialize()
Set mParts = New cParts
End Sub
Property Get Parts() As cParts
Set Parts = mParts
End Property
Property Let Parts(ByVal Value As cParts)
Set mParts = Value
End Property
Property Get Make() As String
Make = mMake
End Property
Property Let Make(ByVal Value As String)
mMake = Value
End Property
Property Get Model() As String
Model = mModel
End Property
Property Let Model(ByVal Value As String)
mModel = Value
End Property
'CLASS MODULE "cCAR" - END


'CLASS MODULE "cPARTS" - BEGIN
Option Explicit
Private mParts As Collection
Private Sub Class_Initialize()
'Initialize stuff here
Set mParts = New Collection
End Sub
Property Get Item(ByVal Index As Variant) As cPart
'Attribute Item.VB_UserMemId = 0
'This property has a hidden attribute to allow
'this class to iterate through members. Uncomment
'this attribute line in a text editor and then import
'back into VBA
On Error Resume Next
Set Item = mParts.Item(Index)
End Property
Property Get NewEnum() As IUnknown
'Attribute NewEnum.VB_UserMemId = -4
'Attribute NewEnum.VB_MemberFlags = "40"
'This property has 2 hidden attributes to allow
'the class to iterate through members. Uncomment
'these attributes in a text editor and then import
'back into VBA
Set NewEnum = mParts.[_NewEnum]
End Property
Property Get Count() As Long
Count = mParts.Count
End Property
Function Add(ByVal Value As String) As cPart
Dim p As cPart
Set p = New cPart
p.Init Value
mParts.Add p, Value
Set Add = p
End Function
Function Remove(ByVal Index As Variant) As Boolean
On Error Resume Next
mParts.Remove Index
Remove = (Err.Number = 0)
End Function
'CLASS MODULE "cPARTS" - END


'CLASS MODULE "cPART" - BEGIN
Option Explicit
Private Const mClassName As String = "PartClass"
Private mName As String
Private mDescription As String
Private Sub Class_Initialize()
mName = ""
mDescription = ""
End Sub
Private Sub Class_Terminate()
'Debug.print "Terminating cPart class" ' <- THIS EMPTY
CLASS_TERMINATE IS THE PROBLEM
End Sub
Friend Function Init(ByVal Value As String) As Boolean
mName = Value
Init = True
End Function
Property Get Name() As String
Name = mName
End Property
Property Let Name(ByVal Value As String)
mName = Value
End Property
Property Get Description() As String
Description = mDescription
End Property
Property Let Description(ByVal Value As String)
mDescription = Value
End Property
'CLASS MODULE "cPART" - END
 
K

keeena

George,
Adding your suggestion fixes the issue. Setting the collection to
nothing in the collection class also seems to fix the issue. Is there
a preference to one over the other?

I assumed the collection would get set to nothing upon destroying the
last collection class reference.

The fact that the behavior only occured while debugging also confused
me.

Thanks for you help!
-K
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top