未封装为函数时CopyMemory导致Excel崩溃的原因排查
为什么VBA CopyMemory代码封装成函数正常,直接写子程序就崩溃?
让我们一步步拆解你遇到的问题,核心差异其实出在几个容易忽略的细节上:
1. 错误的字符串指针获取方式
在崩溃的子程序里,你用了这行代码来获取字符串的内存指针:
CopyMemory pstr1, ByVal VarPtr(str1), 8
这完全是画蛇添足还写错了:
VarPtr(str1)返回的是String变量本身的内存地址,而不是字符串实际数据的指针(也就是BSTR指针)。VBA里的String变量本身就存着BSTR的指针值,你直接用pstr1 = StrPtr(str1)就能拿到正确的字符串数据指针,根本不需要用CopyMemory。- 更致命的是:在32位Excel中,
LongPtr是4字节长度,但你强行用CopyMemory写入8字节数据,这会直接破坏栈上的其他内存数据,瞬间触发Excel崩溃;哪怕是64位Excel,这行代码也是完全冗余的操作。
而你正常运行的版本里(虽然你贴的代码里pstr1没赋值,应该是笔误),正确的写法肯定是pstr1 = StrPtr(str1),这样传入Mem_ReadHex的是合法的BSTR指针,后续Ptr-4才能正确访问到BSTR的长度前缀。
2. 未声明变量导致的内存计算混乱
崩溃版本的子程序里,ub、i、b这三个变量都没加Dim声明。在VBA里如果没开Option Explicit,这些变量会被默认当成Variant类型:
ub = LenB(str1) +5:LenB(str1)是14("PowerVB"是7个Unicode字符,每个占2字节),算出来是19,但Variant类型的数值在参与数组ReDim时,可能出现隐性转换错误,导致数组长度不符合预期。- 循环里的
i和b作为Variant,在访问字节数组和赋值时会有额外的类型转换开销,甚至可能导致内存读写的偏移错误,进一步加剧崩溃风险。
反观正常版本的Mem_ReadHex函数,所有变量都明确声明了类型(Dim bBuffer() As Byte, strBytes() As String, i As Long, ub As Long, b As Byte),这保证了内存操作的精度和可控性。
3. 无效内存访问触发系统保护
因为前面两个错误,崩溃版本里你拿到的pstr1根本不是正确的字符串数据指针,后续pstr1 -4指向的是完全无效的内存地址。当你用CopyMemory去读取这块内存时,系统的内存保护机制会直接终止Excel进程,也就是你看到的崩溃现象。
修复后的可运行子程序
把这些问题修正后,子程序就能正常输出字符串的内存布局了:
Option Explicit ' 一定要开启,强制所有变量必须声明 Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSource As Any, ByVal ByteLen As Long) Sub StringTest() Dim str1 As String Dim pstr1 As LongPtr Dim bBuffer() As Byte, strBytes() As String Dim ub As Long, i As Long, b As Byte ' 明确声明所有变量 str1 = "PowerVB" pstr1 = StrPtr(str1) ' 直接用StrPtr获取正确的BSTR指针 ub = LenB(str1) + 5 ' 对应LenB(str1)+6字节的数组索引范围(0到19) ReDim bBuffer(ub) ReDim strBytes(ub) CopyMemory bBuffer(0), ByVal pstr1 - 4, LenB(str1) + 6 ' 读取合法的内存范围 For i = 0 To ub b = bBuffer(i) strBytes(i) = IIf(b < 16, "0", "") & Hex$(b) Next i Debug.Print "Memory : 0x"; Join(strBytes, "") End Sub
总结一下两个版本的核心区别:
- 正常版本用了正确的指针获取方式+严格的变量类型声明,内存操作精准合法;
- 崩溃版本用了错误的指针赋值方法+未声明变量,导致内存访问越界或破坏栈内存,最终触发崩溃。
内容的提问来源于stack exchange,提问作者Nicholas Humphrey
相关产品推荐
相关产品推荐

