Solved NewEnum Crashes Access

Local time
Today, 06:21
Joined
Aug 10, 2024
Messages
20
The NewEnum get property is causing trouble in some of my Collection wrapper classes I'm trying to write. It seems to work for me in some projects/classes but not in others. I think there's some serious bug here. If I step (F8) through the For-Next loop (in the places where it doesn't work for me) it then works, but when I run through it normally its causes Access to crash.

I've been trying to pinpoint for hours to find out what is triggering this but to no avail. Does anybody else have experienced this problem?

BTW: I know of and tried the /decompile /repair /compact switches.

I'm using Access v16.0 (64 bit).

Attached is a copy of the database. And here's the code. There's not much to see and as simple as possible.

Even if I create a fresh database project and import those files it crashes for me. Perhaps I'm doing something wrong and can't see i, but again it works when stepping (F8) through the loop which tells me this is an Access bug.

Code:
Attribute VB_Name = "Module1"
Option Compare Database
Option Explicit

Dim Coll As New Class2

' This crashes when run (F5). However, when
' stepped (F8) through, it doesn't.
Sub Dump()
    Dim o As Object
    For Each o In Coll
        Debug.Print ObjPtr(o)
    Next
End Sub

' Add some object to the collection.
Sub AddSome()
    Coll.Add New Class1
End Sub

Some dummy class to add to the collection.

Code:
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Class1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Compare Database
Option Explicit

' Empty dummy, to keep it simple.

And here's the minimal custom collection.

Code:
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Class2"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Compare Database
Option Explicit

Private Coll As New Collection

Sub Add(ByVal Class2 As Class1)
    Coll.Add Class2
End Sub

Property Get NewEnum() As IUnknown
Attribute NewEnum.VB_UserMemId = -4
  Set NewEnum = Coll.[_NewEnum]
End Property
 

Attachments

Last edited:
Share the code or a db where it both works and where it doesn't.

Explain in detail what is happening.

You are then much more likely to have folk here be able to help you pinpoint any issues
Ok, attached the database and posted code.
 
Did you try the suggestion in the last answer?
This is just a superset of my posted example, so yes this is what I'm doing. However, I'll have to try to use the Dictionary instead of the Collection.

Keep in mind that it works for me when I single step the code but not when I run it normally.

Update: Dictionary does not support enumeration.
 
Last edited:
Did you try the suggestion in the last answer?
Oh, you are referring to this solution in the thread?

Code:
Public Function NewEnum() As Collection
        Set NewEnum = m_ObjectCollection
End Function

Well, this just returns a reference to the inner collection by a function called NewEnum but not an enumerator. Then you could just make your inner collection public and achieve the same thing.
 
Your example is so poor and not flushed out it is not worth trying to answer why it does not work.
Here is a real example of a fully flushed out custom collection class supporting enumeration and default methods in the class and collection class.

Class
Code:
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Pets"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Compare Database
Option Explicit

Private m_Pets As Collection

Public Function Add(TheName As String, TheAgeInYears As Integer, TheAnimalType As String, TheSound As String) As Pet
  'create a new Pet and add to collection
  Dim NewPet As New Pet
  NewPet.Initialize TheName, TheAgeInYears, TheAnimalType, TheSound
  m_Pets.Add NewPet, NewPet.Name
  Set Add = NewPet
 End Function
Public Sub Add_Pet(ByVal ThePet As Pet)
  'I also add a second Add to allow you to build the object and then assign it
   m_Pets.Add ThePet, ThePet.Name
End Sub
Public Property Get Count() As Long
  Count = m_Pets.Count
End Property
Public Property Get NewEnum() As IUnknown
    Attribute NewEnum.VB_UserMemId = -4
    Attribute NewEnum.VB_MemberFlags = "40"
    'Attribute NewEnum.VB_UserMemId = -4
    'Attribute NewEnum.VB_MemberFlags = "40"
    'This is allows you to iterate the collection "For Each pet in pets"
   Set NewEnum = m_Pets.[_NewEnum]
End Property

Public Property Get Item(Name_Or_Index As Variant) As Pet
   Attribute Item.VB_UserMemId = 0
   'Attribute Item.VB_UserMemId = 0
  'Export the class and uncomment the below in a text editer to allow this to be the default property
  'Then reimport
  Set Item = m_Pets.Item(Name_Or_Index)
End Property

Sub Remove(Name_Or_Index As Variant)
  'remove this person from collection
  'The name is the key of the collection
  m_Pets.Remove Name_Or_Index
End Sub

Public Property Get ToString() As String
  Dim strOut As String
  Dim i As Integer
  For i = 1 To Me.Count
    strOut = strOut & Me.Item(i).ToString & vbCrLf
  Next i
  ToString = strOut
End Property


'----------------------------------------------- All Classes Have 2 Events Initialize and Terminate --------
Private Sub Class_Initialize()
 'Happens when the class is instantiated not related to the fake Initialize method
 'Do things here that you want to run on opening
  Set m_Pets = New Collection
End Sub

Private Sub Class_Terminate()
  'Should set the object class properties to nothing
  Set m_Pets = Nothing
End Sub

Code:
VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Pet"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Compare Database
Option Explicit
'------------------------- Class variables ----------------------
'Normally all class variables are private and you use Lets, Gets, and Sets accessors to retrieve and set.
'Common to start with m to mean 'member' of a class.
Private m_Name As String
Private m_AnimalType As String
Private m_AgeInYears As Integer
Private m_Sound As String


'---------------------------- My Initialize -------------------------------------------------
'Most languages have what is called a parameterized contructor for the class. Does not exist in vba
'This is my way of cheating and I do this on most classes, but not required.
'It is just a method to set a lot of properties

Public Sub Initialize(TheName As String, TheAgeInYears As Integer, TheAnimalType As String, TheSound As String)
  Me.Name = TheName
  Me.AgeInYears = TheAgeInYears
  Me.AnimalType = TheAnimalType
  Me.Sound = TheSound
End Sub

'--------------------------------Property Accessors: Let, Get, Set -------------------------------
Public Property Get AnimalType() As String
  AnimalType = m_AnimalType
End Property

Public Property Let AnimalType(ByVal TheType As String)
  m_AnimalType = TheType
End Property

Public Property Get Name() As String
    Attribute Item.VB_UserMemId = 0
   'Attribute Item.VB_UserMemId = 0
  Name = m_Name
End Property

Public Property Let Name(ByVal TheName As String)
  m_Name = TheName
End Property

Public Property Get Sound() As String
  Sound = m_Sound
End Property

Public Property Let Sound(ByVal TheSound As String)
  m_Sound = TheSound
End Property
Public Property Get AgeInYears() As Integer
  AgeInYears = m_AgeInYears
End Property

Public Property Let AgeInYears(ByVal TheAgeInYears As Integer)
  m_AgeInYears = TheAgeInYears
End Property

'I do this in all classes simply for debuging. A property that returns as string of the other properties
'Notice there is not let procedure

Public Property Get ToString() As String
  ToString = "I am a:" & Me.AnimalType & ", My Name is:" & Me.Name & ", I am: " & Me.AgeInYears & ", The Sound I make: " & Me.Sound
End Property

'------------------------------- Class Methods ------------------------------------------
Public Sub MakeSound()
  MsgBox Me.Sound
End Sub

Enumeration of the custom collection
Code:
Public Sub TestPets()
  Dim myPets As New Pets
  myPets.Add "Toto", 5, "Dog", "Bow wow"
  myPets.Add "Flipper", 8, "Dolphin", "Squeak whistle"
  'Using the other add method
  Dim myPet As New Pet
  myPet.Initialize "Dino", "100", "Dinosaur", "Roar"
  myPets.Add_Pet myPet
  Debug.Print "I have " & myPets.Count & "Pets"
  Debug.Print myPets.Item(1).Name
  'The default works on the collection
  Debug.Print myPets(2).Name
  'The default works on the Pet class and you can use key
  Debug.Print myPets("dino")
  For Each myPet In myPets
    myPet.MakeSound
  Next myPet
  Debug.Print myPets.ToString
  myPets.Remove ("toto")
  Debug.Print "I have " & myPets.Count & " pets"
  myPets(1) = "Tiger"
  Debug.Print myPets(1)
End Sub
 

Attachments

Your example is so poor and not flushed out it is not worth trying to answer why it does not work.
Here is a real example of a fully flushed out custom collection class supporting enumeration and default methods in the class and collection class.
Well, I deleted all the noise around it because it is irrelevant to my question and your readers time. My question simply was why NewEnum crashes and not how to make a complete collection wrapper class with everything included which doesn't answer my question at all. The issue is not that it is incomplete (from your viewpoint) but simply that the enumerator crashes when I run it but works when I single step (F8) through it.

Anyways thanks for your example as a reference. So, when I look at your code, the NewEnum is like mine besides the attribute with the value 40 which, I think, is ignored by VBA (so I read). This tells me that NewEnum is correct.

Anyways I figured out why my example crashes Access (at least for me on my system). It might interest you that it crashes your Pet project as well. Here's a code module that demonstrates it. If you run (F5) First_AddSomePets and then run (F5) Second_IterateAndCrahit crashes Access on my system. If I single step it (F8), the last step works for me.

Now, if I was copying the content of First_AddSomePets into Second_IterateAndCrash, like it is in your test functions, then it works. However, this is a rather theoretical use case as the instantiation of a collection object and adding to it typically happens in different contextes over time, like shown by my example.

Code:
Attribute VB_Name = "CRASH"
Option Compare Database
Option Explicit

Dim myPets As New Pets

' Run this first (F5)
Sub First_AddSomePets()
  myPets.Add "Toto", 5, "Dog", "Bow wow"
  myPets.Add "Flipper", 8, "Dolphin", "Squeak whistle"
End Sub

' Run this second (F5) which crashes on me.
' Althought it won't crash when stepped (F8) through.
Sub Second_IterateAndCrash()
    Dim myPet As Pet
    For Each myPet In myPets
        Debug.Print myPet.Name
    Next
End Sub
 
The NewEnum get property is causing trouble in some of my Collection wrapper classes I'm trying to write. It seems to work for me in some projects/classes but not in others. I think there's some serious bug here. If I step (F8) through the For-Next loop (in the places where it doesn't work for me) it then works, but when I run through it normally its causes Access to crash.
Further looking into this with more examples:
Code:
' Crashes
Sub Crash()
    AddSome
    AddSome
    Dump
End Sub

No crash here:
Code:
Sub NoCrash()
    AddSome
    AddSome

    Dim o As Object
    For Each o In Coll
        Debug.Print ObjPtr(o)
    Next
End Sub

Again no crash:
Code:
Sub Dummy()
End Sub

' No crash triggered.
Sub Dump2()
    Dummy
    
    Dim o As Object
    For Each o In Coll
        Debug.Print ObjPtr(o)
    Next
End Sub

While experimenting I sometimes could get an error message telling me that the automation object has disconnected from the client. Given all this it looks to me this is a bug.
 
I have never had a crash when used with 100s of collection classes.
 
Last edited:
The NewEnum get property is causing trouble in some of my Collection wrapper classes I'm trying to write. It seems to work for me in some projects/classes but not in others. I think there's some serious bug here. If I step (F8) through the For-Next loop (in the places where it doesn't work for me) it then works, but when I run through it normally its causes Access to crash.
I updated Access to Build 2406 17726.20206 and the problem went away.

I think I was on 2405.
 

Users who are viewing this thread

Back
Top Bottom