2

I've been working on a Visual Basic script for a colleague some time that has been exhibiting an weird bug that I just cannot get to the bottom of. When run, the script sometimes works fine, but a lot of the time gives me a "Subscript out of range" error that promptly crashes Excel.

The first thing I'd like to know is why Excel crashes, even when I have error-catching code in the script. At the start of the script I have the line:

On Error GoTo ErrorCatch

With the following lines at the bottom:

ErrorCatch:

    MsgBox "Error " & Err.Number & " detected - " & Err.Description _
            & ".  DebugCheckpoint = " & DebugCheckpoint & "."
    Err.Clear
    DisableSpeedBoosts

DebugCheckpoint is a character string that is changed at various points during the script to help narrow down where the script crashes. The last line of that refers to a pair of functions within a separate module of the same spreadsheet. At the start, I run the following function:

Sub EnableSpeedBoosts()

    Application.Calculation = xlCalculationManual
    Application.ScreenUpdating = False
    Application.DisplayStatusBar = False
    Application.EnableEvents = False

End Sub

While I have the reverse at the end:

Sub DisableSpeedBoosts()

    'Restore previous settings
    Application.Calculation = xlCalculationAutomatic
    Application.ScreenUpdating = True
    Application.DisplayStatusBar = True
    Application.EnableEvents = True

End Sub

However, the script still crashes Excel even with that line commented out, so it may not be relevant.

Can anyone explain why Excel still crashes, even though the error has been caught and cleared? That's the first thing that doesn't make sense.

As for the script itself, what I am trying to do is populate a table of master data on the first sheet from a similarly-structured table in an import file, while checking for actual and potential duplicate lines. What's supposed to happen is something like this:

  • The user run the script and selects the import file through a dialog box,

  • If a valid file is chosen, the script runs some basic checks on the first sheet to see if the table on that sheet conforms to a set format,

  • If that check passes, the script then populates an array (ImportArray) using that table and a second array from the master table (DataArray).

  • The script also initialises two blank arrays, one for the items to be added to the master table (AddArray), and one for the actual and potential duplicates to be added to a second table on the same spreadsheet (LogArray).

  • The script then goes through the ImportArray, runs a number of checks for actual and potential duplicate lines between this and the DataArray, and adds the line to the AddArray, or the LogArray, or both, in the case of potential duplicates.

  • Finally, once it reaches the end of the ImportArray, the script appends the AddArray to the master table and the LogArray to the duplicates table, prints a friendly message, and exits.

This may not be the best or most efficient way of doing this, but I think the logic is fairly sound and when it works it works quite well. However, there's a weird, intermittent bug in the code that I cannot explain.

When I first created this, the code ran fine on my machine, but promptly crashed my colleagues computer. Both of us are using Excel 2013 on Windows 7 on company comuters, so there's not an awful lot of scope for customisations or variations, but the script worked fine on my computer but crashed on his. The error message is always the same, some form of "Subscript out of range" error, but I've not been able to explain why, especially since the exact same script was able to import the exact same file on my computer with no problems.

Running some more tests and adding some debug code, I've now been able to reliably re-create the error on my computer. I've narrowed the crash to a single line, but it now makes even less sense than before. The last line in the following snippet is crashing not just the script, but Excel as well:

If (PopulateArray(ImportArray) = False) Then

    MsgBox "Failed to load import file.  Aborting."
    Exit Sub

End If

DebugCheckpoint = "ImportArray tests"
Debug.Print "ImportArray tests"
Debug.Print "ImportArray = (" & LBound(ImportArray, 1) & " to " & UBound(ImportArray, 1) _
        & ", " & LBound(ImportArray, 2) & " to " & UBound(ImportArray, 2) & ")"
' Crashes on the next line
Debug.Print "ImportArray(1, 1) = " & ImportArray(1, 1)

The PopulateArray function prompts the user for an import file and runs some validation on the file. If no file is selected or the validation tests fail it returns false. The second-to-last Debug.Print line displays the dimensions of the ImportArray, while the one that crashes it simply tries to display the first element. Here's what the Immediate window looks like using a valid import file:

ImportArray tests
ImportArray = (1 to 143, 1 to 5)

The line "Debug.Print "ImportArray(1, 1) = " & ImportArray(1, 1)" doesn't show up on this window, so presumably is the one crashing the script, but I don't understand why. We know from the previous line that the array has 143 x 5 records both with base one, so how can item (1, 1) be "out of range?" I've tried various pieces of code in different parts of the script to try and explain what's happening but to no avail. The array just seems to randomly disappear.

Can anyone explain what's going on here, or at least give me some pointers to try and investigate this further. Even though I've narrowed the crash to one line, I can see absolutely no reason why that line is causing the crash. I also can't see why it used to work fine but now it crashes, when all I've done is add some debug code.

Please help me understand what's going on if you can.


EDIT:

OK, I've narrowed this down quite a bit and added some error-checking code to the Populate() function so it should be easier to see what's going on now. Here's the main script in it's entirety:

Sub ImportArrayTest()

    Dim ImportArray() As Variant

    If (PopulateArrayTest(ImportArray) = False) Then

        MsgBox "Failed to load import file.  Aborting."
        Exit Sub

    End If

    Debug.Print "ImportArray tests"
    Debug.Print "ImportArray = (" & LBound(ImportArray, 1) & " to " & UBound(ImportArray, 1) _
            & ", " & LBound(ImportArray, 2) & " to " & UBound(ImportArray, 2) & ")"
    Debug.Print ImportArray(3, 3)


End Sub

While this is the trimmed-down Populate() function:

Function PopulateArrayTest(ImportTable As Variant) As Boolean

    Dim ImportFile As Workbook
    Dim ImportFileName As Variant
    PopulateArrayTest = False

    ImportFileName = Application.GetOpenFilename("Excel (*.xls*),*.xls*", 1, "Please select report")
    Set ImportFile = Application.Workbooks.Open(ImportFileName)

    With ImportFile.Worksheets(1)

        ImportTable = .Range(.Cells(5, 1), .Cells(.Range("a5").End(xlDown).Row, 5))

    End With

    Debug.Print "ImportTable tests"
    Debug.Print "ImportTable = (" & LBound(ImportTable, 1) & " to " & UBound(ImportTable, 1) _
        & ", " & LBound(ImportTable, 2) & " to " & UBound(ImportTable, 2) & ")"
    Debug.Print "ImportTable (1, 1) = " & ImportTable(1, 1)     

    PopulateArrayTest = True
    ImportFile.Close

End Function

The script now crashes on the line "Debug.Print ImportTable(1, 1)" in the PopulateArrayTest function and takes Excel with it. The output in the immediate window looks like this:

ImportTable tests
ImportTable = (1 to 143, 1 to 5)

So the ImportTable is being correctly re-sized according to the size of the table in the import file, but accessing it gives a "Subscript out of range error." I really can't see what the problem is here. Can anyone see what I'm missing?

8
  • 1
    to actually narrow it down, you simply remove any On Error Go To statements so that the macro will fail at the very line throwing the first error and popping up a dialog box where you will press "Debug" to jump to the offending line Commented Jun 7, 2016 at 14:05
  • If calling PopulateArray causes Excel to crash then that's the code you need to post. Commented Jun 7, 2016 at 16:25
  • @Tim Williams Calling PopulateArray seems to work fine. The crash occurs later when I try to access the data. However, I'll look into that more to see if that's an issue. Commented Jun 7, 2016 at 16:28
  • Regardless, you should post all the relevant code. Your post has too much text, not enough code. Second-hand debugging is no fun. Commented Jun 7, 2016 at 16:31
  • @Tim Williams Understood, but I've been trying to solve this one for a while now. The bug is intermittent - sometimes the script works, sometimes it doesn't - so I wouldn't expect you to be able to find or even re-produce it. Rather I'm hoping to find pointers or strategies to help me understand why it's crashing. Commented Jun 7, 2016 at 16:51

1 Answer 1

1

The problem is that your types don't match completely. The rule here is that the data type of the array declared in the calling procedure must match the data type declared in the called procedure's parameter list. You pass an Variant Array to a Variant Parameter. This doesn't work in VBA with ByRef Parameter so the ImportArray will be empty in your ImportArrayTest method.

To avoid those problems you should change the Function to return a Variant Array instead of passing it back as a ByRef Parameter like this:

Function PopulateArrayTest() As Variant()

and call it like this:

 Dim ImportArray() As Variant
 ImportArray = PopulateArrayTest()
Sign up to request clarification or add additional context in comments.

3 Comments

OK, I think that might be it. I did a quick fix of the code there and it ran through. I'll have to have a more thorough look to see if it's all working as expected, but it looks promising. Thank you so much for taking the time to look at this. You've no idea how long I've been trying to fix this one.
I know it sounds weird at first. But it seems this is a restriction for ByRef parameters in VBA. A link which handles the topic is here: cpearson.com/excel/passingandreturningarrays.htm
I've accepted your answer as the correct one as the code now works as expected. I did have a little trouble returning an empty array in the case of the user selection or validation steps failing, but I've wrapped the line ImportArray = PopulateArrayTest() in an On Error Resume Next statement which catches the problem. Possibly not the best way of doing this, but good enough for now. The script is only used on one import file once a month, so a workable hack suits my purposes. Thanks so much for your help with this. I would not have figured that out on my own.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.