从VBA的vbNullString认识API参数传递

2021-03-18 16:32:59 浏览数 (1)

最近在写个简单的程序时,用到了API FindWindow,复制声明后,直接就把代码写了,可是发现一直找不到窗口,代码:

代码语言:javascript复制
Sub testFindWindow()    Dim str As String        str = "新建文本文档.txt - 记事本"        Debug.Print str, FindWindow("", str)End Sub

然后自己就以为是不是窗口名称写错了,检查好久才发现是第一个参数错了!

其实这个参数要传递vbNullString一直是知道的,但没有仔细想过,知其然不知其所以然,正确代码应该是:

代码语言:javascript复制
Sub testFindWindow()    Dim str As String        str = "新建文本文档.txt - 记事本"        Debug.Print str, FindWindow(vbNullString, str)End Sub

这个错误是只使用VBA的人容易犯的吧,总以为空字符""和vbNullString是一回事,那么,它们有什么区别呢?

""和vbNullString

首先查看帮助文件:vbNullString 值为 0 的字符串,用来调用外部过程;与长度为零的字符串 ("") 不同

帮助文件已经告诉我们这2个是不一样的,可是我们使用VBA再写个简单的测试代码又会发现奇怪的地方:

代码语言:javascript复制
Sub TestNullString()    Dim str As String        Debug.Print str, str = "", str = vbNullString        str = ""    Debug.Print str, str = "", str = vbNullString        Dim v1 As Variant, v2 As Variant    v1 = VBA.Strings.StrComp(str, "", vbBinaryCompare)    v2 = VBA.Strings.StrComp(str, vbNullString, vbBinaryCompare)        Debug.Print str, v1, VBA.IsNull(v2)End Sub
'输出:              True          True              True          True               0            False

从输出来看,用=和StrComp进行对比,这2个东西是相同的!这又是为什么呢!

这里只能进行猜测了,VBA在比较2个字符串的时候,可能是先读取长度,如果都为0,则判断为相同了,""和vbNullString在使用LEN函数的时候,返回的都是0。

那么,它们2个不同之处在哪里呢?

这个可以使用Strptr来查看,""这个是分配了地址的,vbNullString是没有初始化的,这就是它们2个的最大不同之处:

代码语言:javascript复制
Sub TestSrtPtr()    Debug.Print StrPtr(""), StrPtr(vbNullString)End Sub'输出 163726236     0 

那么在使用API传递String类型参数的时候,如果需要传1个空字符,非得要vbNullString吗?

既然vbNullString是一个没有初始化的字符串,那么,其实这样传递也是一样:

代码语言:javascript复制
Sub testFindWindow()    Dim str As String    Dim tmp As String        str = "新建文本文档.txt - 记事本"        Debug.Print str, FindWindow(tmp, str)  End Sub

这样就可以得到正确结果了,声明了一个tmp变量,在没有初始化的时候,它就是一个vbNullString。

API String类型参数传递

从帮助文件中知道,vbNullString 值为 0 的字符串,如果真的传递0过去,很明显也是不行的,数据类型就不对,所以这个只是一个标志,VBA编译器会具体去处理这种情况。

对API的参数传递,VBA为我们做了太多了,以至于使用者不需要明白底层原理就可以简单的使用。

如果了解一点C语言的知识,我们就能大概理解了。

在C语言里,并没有String类型,只有Char类型(也就是VBA里的Byte),而API里的String类型其实就是Char数组的指针,VBA在API参数传递的时候,碰到String类型,它又帮我们做了什么?

VBA会帮使用者将VBA的String类型首先从Unicode转换为ANSI编码,然后取出转换后的Char数组的第一个地址,再将这个地址传递给了API,API如果有返回值,VBA就会做一个相反的操作,测试代码:

代码语言:javascript复制
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPrivate Declare Function FindWindowByPtr Lib "user32" Alias "FindWindowA" (ByVal lpClassName As Long, ByVal lpWindowName As Long) As Long
Sub testFindWindowPtr()    Dim str As String    Dim tmp As String        str = "新建文本文档.txt - 记事本"        Debug.Print str, FindWindow(vbNullString, str)    Debug.Print str, FindWindowByPtr(0, StrPtr(str))        Dim strANSI As String    strANSI = VBA.StrConv(str, vbFromUnicode)    Debug.Print str, FindWindowByPtr(0, StrPtr(strANSI))End Sub'输出:新建文本文档.txt - 记事本    67244 新建文本文档.txt - 记事本    0 新建文本文档.txt - 记事本    67244

这里声明了一个FindWindowByPtr函数,只是把FindWindow的参数由String类型修改为了Long类型,从代码的输出过程就可以看到,如果不进行String类型的编码转换,FindWindowByPtr得不到正确的结果。

而在FindWindow里,VBA编译器自动为使用者做了处理,所以我们在使用API的时候,根本就不需要关注这个。这也容易造成VBA使用者不清楚原理,出现错误的时候很难找到具体的原因。

0 人点赞