tl;博士
你把IsMissing 弄糊涂了,让它认为一个未初始化的数组是一个格式错误的ParamArray,并在其实现中遇到了一个错误。
根据 Windows 事件查看器,导致 Excel 崩溃的潜在异常是 vbe7.dll 中的访问冲突:
我看到的第一个报告(上面有评论)是 Rubberduck 回调失败,因为 Excel 是吐司。
Faulting application name: EXCEL.EXE, version: 15.0.5067.1000, time stamp: 0x5b76360d
Faulting module name: VBE7.DLL, version: 7.1.10.68, time stamp: 0x58def301
Exception code: 0xc0000005
Fault offset: 0x00000000001cd38c
Faulting process id: 0xac4
Faulting application start time: 0x01d45de81ef072ed
Faulting application path: C:\Program Files\Microsoft Office\Office15\EXCEL.EXE
Faulting module path: C:\PROGRA~1\COMMON~1\MICROS~1\VBA\VBA7.1\VBE7.DLL
访问冲突是由于 VB 运行时试图取消引用空指针。这是调试器在它死亡时登陆你的地方:
00007FFF9CDDD383 mov rax,qword ptr [rsp+30h]
00007FFF9CDDD388 mov rax,qword ptr [rax+8]
00007FFF9CDDD38C movzx eax,word ptr [rax] <--fails, rax is 0.
00007FFF9CDDD38F cmp eax,1
00007FFF9CDDD392 jne 00007FFF9CD7B8A4
那么,让我们看看IsMissing function 正在检查什么。 VBA 基本上是 COM 的野兽,调用函数接收未传递的可选参数作为Variant,每个 RPC 约定的类型为 VT_ERROR。它不会作为Null、Empty 或任何其他VB 构造传递。以下代码演示:
Sub Foo()
Bar
End Sub
Sub Bar(Optional x)
Debug.Print VarPtr(x) 'This has a valid pointer.
Debug.Print IsError(x) 'True
Debug.Print VarType(x) '10 (vbError)
End Sub
您可以阅读更多技术解释on Raymond Chen's blog。
IsMissing 打算传递一个传入参数,因此它还必须注意ParamArrays(在后台,ParamArray 只是一个Variant()) .虽然,如果你不给它任何参数,那么过程仍然会得到一个数组——它只是有一个比LBound低的UBound:
Sub Calling()
Called
End Sub
Sub Called(ParamArray params())
Debug.Print LBound(params) '<-- 0
Debug.Print UBound(params) '<-- -1
End Sub
这就是有趣的地方。您不能在同一过程签名中同时拥有 ParamArray 和可选参数 - 它可以是其中一个。查看函数签名时,您会发现它需要一个指向 Variant 的指针(即,它需要一个 ByRef 参数):
[entry(0x60000007), helpcontext(0x000f69b5)]
VARIANT_BOOL _stdcall IsMissing([in] VARIANT* ArgName);
但是像这样的代码并没有给函数它所期望的样子:
Sub DieExcelDie()
Dim x(1), y()
x(1) = y
x(1) = IsMissing(x(1))
End Sub
x(1) 必须编组为 ByRef Variant 才能作为参数传递给 IsMissing。所以它看到是Variant 的数组,LBound 和UBound 的值都为零(this answer 有点像未初始化的数组看起来像在内存中)。该参数尚未通过 RPC 机制,因此这一点与包含一个传递参数的ParamArray 完全无法区分。它将检查变量是否被键入为 VT_ERROR,查看它是否是一个数组,并尝试以这种方式处理它。由于它不符合空参数列表的规范,它可能会通过取消引用数据区域来检查第一个传递的参数是什么。 SAFEARRAY 结构如下所示1:
回到反汇编并没有完全进入反汇编 vbe7.dll 的兔子洞,上一条指令中的 rax+8 很可能加载了 pvData 指针(在 64 位版本的Excel),你可以看到是零。
这只是IsMissing 实现中的一个错误——它应该检查空指针而不是盲目地取消引用它。我怀疑,虽然无法验证,但它是在 VB5 中引入的,以及允许非Variant 可选参数和默认值的更改。
1图片是codeguru上How Visual Basic 6 Stores Data的截图。