【问题标题】:VBA - subscript out of range error crashing ExcelVBA - 下标超出范围错误导致 Excel 崩溃
【发布时间】:2016-06-07 13:47:08
【问题描述】:

一段时间以来,我一直在为一位同事编写一个 Visual Basic 脚本,该脚本出现了一个我无法深入了解的奇怪错误。运行时,脚本有时可以正常工作,但很多时候会出现“下标超出范围”错误,导致 Excel 立即崩溃。

我想知道的第一件事是为什么 Excel 会崩溃,即使脚本中有错误捕获代码也是如此。在脚本的开头我有一行:

On Error GoTo ErrorCatch

底部有以下几行:

ErrorCatch:

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

DebugCheckpoint 是一个字符串,它会在脚本执行过程中的不同时间点进行更改,以帮助缩小脚本崩溃的范围。最后一行是指同一电子表格的单独模块中的一对函数。一开始,我运行以下函数:

Sub EnableSpeedBoosts()

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

End Sub

虽然我在结尾处有相反的内容:

Sub DisableSpeedBoosts()

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

End Sub

但是,即使该行被注释掉,该脚本仍然会使 Excel 崩溃,因此它可能不相关。

谁能解释为什么 Excel 仍然崩溃,即使错误已被捕获并清除?这是没有意义的第一件事。

至于脚本本身,我要做的是从导入文件中结构相似的表中填充第一张表上的主数据表,同时检查实际和潜在的重复行。 应该发生的是这样的:

  • 用户运行脚本,通过对话框选择导入文件,

  • 如果选择了有效文件,脚本会在第一张工作表上运行一些基本检查,以查看该工作表上的表格是否符合设定的格式,

  • 如果检查通过,则脚本会使用该表和主表中的第二个数组 (DataArray) 填充一个数组 (ImportArray)。

  • 该脚本还会初始化两个空白数组,一个用于添加到主表的项目 (AddArray),另一个用于将实际和潜在的重复项添加到同一电子表格的第二个表 (LogArray )。

  • 然后脚本会遍历 ImportArray,在此和 DataArray 之间运行一些实际和潜在重复行的检查,并将该行添加到 AddArray 或 LogArray 或两者中,在以下情况下潜在的重复。

  • 最后,一旦到达 ImportArray 的末尾,脚本将 AddArray 附加到主表,将 LogArray 附加到重复表,打印一条友好消息,然后退出。

这可能不是最好或最有效的方法,但我认为逻辑相当合理,并且当它工作时效果很好。但是,代码中有一个奇怪的、间歇性的错误,我无法解释。

当我第一次创建这个时,代码在我的机器上运行良好,但很快就让我同事的电脑崩溃了。我们俩都在公司通勤者的 Windows 7 上使用 Excel 2013,因此自定义或变化的范围并不大,但脚本在我的计算机上运行良好,但在他的计算机上崩溃了。错误消息总是相同的,某种形式的“下标超出范围”错误,但我无法解释原因,特别是因为完全相同的脚本能够在我的计算机上导入完全相同的文件而没有问题.

运行更多测试并添加一些调试代码,我现在能够可靠地在我的计算机上重新创建错误。我已将崩溃范围缩小到一行,但现在它比以前更没有意义了。以下 sn-p 中的最后一行不仅会导致脚本崩溃,还会导致 Excel 崩溃:

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)

PopulateArray 函数提示用户输入一个导入文件并对文件运行一些验证。如果未选择文件或验证测试失败,则返回 false。倒数第二个 Debug.Print 行显示 ImportArray 的尺寸,而崩溃的那一行只是尝试显示第一个元素。下面是使用有效导入文件的即时窗口的外观:

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

"Debug.Print "ImportArray(1, 1) = " & ImportArray(1, 1)" 行没有显示在此窗口中,所以大概是脚本崩溃的那个,但我不明白为什么。我们从上一行知道该数组有 143 x 5 条记录,都以 1 为基数,那么 item (1, 1) 怎么会“超出范围”呢?我在脚本的不同部分尝试了各种代码来尝试解释正在发生的事情,但无济于事。数组似乎只是随机消失。

谁能解释一下这里发生了什么,或者至少给我一些建议来尝试进一步调查。即使我已将崩溃范围缩小到一条线,我也完全看不出那条线导致崩溃的原因。我也不明白为什么它以前可以正常工作,但现在它崩溃了,而我所做的只是添加一些调试代码。

如果可以的话,请帮助我了解发生了什么。


编辑:

好的,我已经缩小了很多范围,并在 Populate() 函数中添加了一些错误检查代码,这样应该更容易看到现在发生了什么。这是完整的主要脚本:

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

虽然这是精简后的 Populate() 函数:

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

脚本现在在 PopulateArrayTest 函数中的“Debug.Print ImportTable(1, 1)”行崩溃,并使用 Excel。即时窗口中的输出如下所示:

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

因此 ImportTable 正在根据导入文件中表的大小正确调整大小,但访问它会给出“下标超出范围错误”。我真的看不出这里有什么问题。谁能看到我错过了什么?

【问题讨论】:

  • 要真正缩小范围,您只需删除所有On Error Go To 语句,以便宏将在抛出第一个错误的那一行失败并弹出一个对话框,您将在其中按“调试”跳到违规行
  • 如果调用 PopulateArray 导致 Excel 崩溃,那么这就是您需要发布的代码。
  • @Tim Williams 呼叫PopulateArray 似乎工作正常。当我尝试访问数据时,稍后会发生崩溃。不过,我会进一步调查,看看这是否是个问题。
  • 无论如何,您应该发布所有相关代码。你的帖子有太多的文字,没有足够的代码。二手调试不好玩。
  • @Tim Williams 明白了,但我一直在尝试解决这个问题。该错误是间歇性的-有时脚本有效,有时无效-因此我不希望您能够找到甚至重新生成它。相反,我希望找到指针或策略来帮助我理解它为什么会崩溃。

标签: arrays vba excel


【解决方案1】:

问题是您的类型不完全匹配。这里的规则是调用过程中声明的数组的数据类型必须与被调用过程的参数列表中声明的数据类型相匹配。您将变体数组传递给变体参数。这在带有 ByRef 参数的 VBA 中不起作用,因此 ImportArray 在您的 ImportArrayTest 方法中将为空。

为避免这些问题,您应该更改函数以返回一个变体数组,而不是将其作为 ByRef 参数传回,如下所示:

Function PopulateArrayTest() As Variant()

然后这样称呼它:

 Dim ImportArray() As Variant
 ImportArray = PopulateArrayTest()

【讨论】:

  • 好的,我想可能是这样。我在那里快速修复了代码,它运行了。我必须进行更彻底的查看,看看它是否按预期工作,但它看起来很有希望。非常感谢您花时间看这个。你不知道我尝试修复这个问题多久了。
  • 我知道一开始听起来很奇怪。但似乎这是对 VBA 中 ByRef 参数的限制。处理该主题的链接在这里:cpearson.com/excel/passingandreturningarrays.htm
  • 我已经接受您的答案是正确的,因为代码现在可以按预期工作。在用户选择或验证步骤失败的情况下,我确实在返回一个空数组时遇到了一些麻烦,但我已经将ImportArray = PopulateArrayTest() 行包裹在一个On Error Resume Next 语句中,它可以解决问题。可能不是最好的方法,但现在已经足够了。该脚本每月仅在一个导入文件上使用一次,因此可行的 hack 适合我的目的。非常感谢您对此的帮助。我自己不会想出来的。
猜你喜欢
  • 2014-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-02
  • 1970-01-01
相关资源
最近更新 更多