一. 前言
最近碰到一个比较有趣的vba
编写的错误, 处理该问题时, 突然想起很早之前的一个vba
语言层的bug
.
相关内容见本人在这个帖子上的描述, @liucqa你的问题暂时解决了_VBA字符串/字符编码/字符集谜题-Excel VBA程序开发-ExcelHome技术论坛 -.
二. 问题
该问题为语言层级的bug
所导致.
该问题在百度上能够检索到的相关页面最早见于可能是在2010
版本的office
上出现.
问题很简单, 当使用instr, split
函数处理字符串时, 部分的字符串上会出现异常错误.
Sub test()
Debug.Print InStr(1, ChrW$(12500), "a", vbTextCompare)
End Sub
' vbTextCompare, 进行文本模式比较
Option Explicit
Sub test()
Dim s As String
Dim i As Long, k As Long, ic As Long
Dim arr() As Long
On Error Resume Next
ic = 0
For i = 1 To 65535
s = ChrW$(i)
k = InStr(1, s, "a", vbTextCompare)
If Err.Number > 0 Then
ReDim Preserve arr(ic)
arr(ic) = i
ic = ic + 1
Err.Clear
End If
Next
If ic > 0 Then Cells(2, 1).Resize(ic, 1).Value = Application.Transpose(arr)
End Sub
通过上述代码得到以下可以导致异常错误字符串的Unicode
, 使用excel
的UNICHAR
函数生成对应字符.
异常unicode | 异常字符 |
---|---|
12460 | ガ |
12462 | ギ |
12464 | グ |
12466 | ゲ |
12468 | ゴ |
12470 | ザ |
12472 | ジ |
12474 | ズ |
12476 | ゼ |
12478 | ゾ |
12480 | ダ |
12482 | ヂ |
12485 | ヅ |
12487 | デ |
12489 | ド |
12496 | バ |
12497 | パ |
12499 | ビ |
12500 | ピ |
12502 | ブ |
12503 | プ |
12505 | ベ |
12506 | ペ |
12508 | ボ |
12509 | ポ |
12532 | ヴ |
12535 | ヷ |
12536 | ヸ |
12537 | ヹ |
12538 | ヺ |
12542 | ヾ |
可以看到这些导致异常的字符均为日文, 呈现一定的规律性, 间隔多为1 - 3
之间.
三. 小结
理论上, 32
位和64
位应该是一样的, 32位只测试了2013, 2016测试了32位, 64位, 其余的只测试64位.
以下均为office
中文版(简体), 专业增强版本.
在win8.1, win10
下进行的测试.
经过实际测试, 结果如下:
office版本 | 是否出现bug |
---|---|
2013 | yes |
2016 | yes |
2019 | no |
2021 | no |
在二进制(vbBinaryCompare
)下的比较并未出现任何异常, 为默认状态下instr
函数使用参数, 即不区分大小写.
Option Explicit
Sub test()
Dim s As String
Dim i As Long, k As Long, ic As Long
Dim arr() As Long
On Error Resume Next
ic = 0
For i = 1 To 65535
s = ChrW$(i)
k = InStr(1, s, "a", vbBinaryCompare)
If Err.Number > 0 Then
ReDim Preserve arr(ic)
arr(ic) = i
ic = ic + 1
Err.Clear
End If
Next
If ic > 0 Then Cells(2, 1).Resize(ic, 1).Value = Application.Transpose(arr)
End Sub
虽然微修复该bug
前后间隔将近十年, 也许微软还不希望vba
彻底入土为安(微软自VBE7发布之后已经基本没有再为VBA显著添加些什么).
这种涉及到字符(编码)的问题, 对于vba这个不在更新的古老语言而言, 基本是等同于癌症, 只能化疗维持下去.
四. 延展
判断某个字符串是否出现在另一个字符串上, 除了instr
函数, 也可以使用StrStr
系列api
来实现(使用api
需要谨慎, 非熟(高)手不建议在业务中的vba
代码使用api
, 导致的异常错误可能导致文件彻底被破坏, 信息不可找回).
PCWSTR StrStrNW(
[in] PCWSTR pszFirst,
[in] PCWSTR pszSrch,
UINT cchMax
);
' 32位
Private Declare Function uStrStrNW Lib "Shlwapi.dll" Alias "StrStrNW" (ByVal sText As Long, ByVal sFind As Long, ByVal iLength As Long) As Long '大小写不明感
Private Declare Function uStrStrW Lib "Shlwapi.dll" Alias "StrStrW" (ByVal sText As Long, ByVal sFind As Long) As Long
' 64位
Private Declare PtrSafe Function uStrStrNW Lib "Shlwapi.dll" Alias "StrStrNW" (ByVal sText As LongLong, ByVal sFind As LongLong, ByVal iLength As Long) As Long '大小写不明感
Private Declare PtrSafe Function uStrStrW Lib "Shlwapi.dll" Alias "StrStrW" (ByVal sText As LongLong, ByVal sFind As LongLong) As Long
Sub test()
Dim a As String
Dim b As String
a = ChrW$(12500)
b = "a"
Debug.Print uStrStrW(StrPtr(a), StrPtr(b))
End Sub
但是需要注意返回的是匹配字符的内存地址(不匹配返回0), 不是字符串出现的位置, 上述函数可以判断是否有出现的字符串, 但是不能判断位置.
Returns the address of the first occurrence of the matching substring if successful, or NULL otherwise.
鉴于操作内存是极为危险的, 微软并未官方提供相关文档Unofficial Documentation for VarPtr, StrPtr, and ObjPtr (classicvb.net)的支持, StrPtr
返回的是字符串在内存中的地址. 在64
位下, 需要使用longlong
数据类型, longlong
类型数据的支持和VBE7
是微软最后为vba
留下的不多的遗产了.
操作内存通常在大型文本数据的处理上, 如在数百兆大小文本中查找特定的字符串, 对大型数组的文本进行排序等, 通过直接访问内存, 可以极大的提高数据处理的速度.