加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

澄清VB调用API时字符串参数的困惑

发布时间:2020-12-16 22:51:22 所属栏目:大数据 来源:网络整理
导读:声明:本文部分内容来源于网络! 首先,我认为这样花费精力去研究VB调用API字符串的种种猫腻是很有必要的。本着严谨和发现问题与解决问题的原则,和为了能更好的认识问题的本质,我特写了这篇冗长的讨论。网上有很多关于此的讨论,但比较杂乱,目的不明确,

声明:本文部分内容来源于网络!

首先,我认为这样花费精力去研究VB调用API字符串的种种猫腻是很有必要的。本着严谨和发现问题与解决问题的原则,和为了能更好的认识问题的本质,我特写了这篇冗长的讨论。网上有很多关于此的讨论,但比较杂乱,目的不明确,也很难懂。在此也就是做个总结吧!让大家能有一个清楚认识。

我的问题是从调用内存API时参数的ByVal VarPtr+变量和直接写变量的区别开始的。举个例子:ByVal VarPtr x 与x,按照网上的说法这两者是没有任何区别的,这是不正确的!这两个有没有区别要看变量x是什么类型的,如果是字符串型,这两个是完全不一样的含义;如果是除了字符串型以外的任何类型(也不包括变体型),这两个的含义就是一样的!为什么这样说?往下看你就明白了。

先说说VB中字符串的存储结构。VB里面使用的字符串String就是COM规范中的BSTR ,其特点是缓冲区前面有4字节的前缀用于说明缓冲区的长度:

在VB中字符串型的变量实际上不是字符串型,而是一个Long型的变量,它里面存放的是指向字符串缓冲区的指针(也就是指向图中蓝色部分的起始地址)。知道了VB字符串的结构,重点就来了。VB中的字符串全是以Unicode编码表示的,而API中的字符串是以ASNI表示的。所以在调用API涉及到字符串参数时VB妈妈就自动帮程序员完成了由Unicode到ASNI的转换。也就是说你写一个API,VB妈妈只要看到这个API参数中有字符串,在传递参数前,就自动帮你在内存中建立一个临时的变量,用来存放转换Unicode到ASNI转换的结果,一切对于字符串的操作都是对这个临时的变量处理的,这时候你原来的变量就一边歇着去了,直到处理完毕,细心的VB妈妈又把临时变量的值给原来的字符串变量。问题就出在这!就是这个转换过程使得ByVal VarPtr x 与x有着天壤之别!(第一遍看不到就多读几遍)

看一个例子:

Sub SwapPtr(sA As String,sB As String)

Dim lTmp As Long

CopyMemory lTmp,sA,4

CopyMemory sA,sB,4

CopyMemory sB,lTmp,4

End Sub

Sub SwapPtr(sA As String,ByVal VarPtr(sA),4

CopyMemory ByVal VarPtr(sA),ByVal VarPtr(sB),4

CopyMemory ByVal VarPtr(sB),4

End Sub

这是交换两个字符串的函数,第一个是错误的,第二个是正确的,这两个函数的差别就在于有没有ByVal VarPtr,如果按照网上的说法,这两段代码是一样的,但是事实证明是不一样的!

下面我们来分析这两段代码。宏观上看这两段代码都是要取出变量中存放的缓冲区的指针,然后交换,所以定义了一个long型的变量lTmp来临时存放这个指针。但是只有第二段代码成功了。第一个为什么不行?按照普通的理解:VB妈妈首先发现sA是个字符串参数,先进行UA转换。转换完成之后按照传地址的方式传给CopyMemory临时变量的地址,然后寻址成功之后获取的是临时变量中的ASNI字符串缓冲区的指针,再把这个32位long型的指针给lTmp。貌似有了这个指针,就不愁找不到真正的字符串。但不要忽略一个问题:这是临时的变量!临时缓冲区!当CopyMemory lTmp,4这句结束之后,这个缓冲区就不复存在了!对于CopyMemory sA,4这句话,按照上面的分析,sA,sB都是字符串型,所有都进行转换,都有临时变量,这两个临时变量传入自己的地址,CopyMemory找到地址中的内容,也就是缓冲区指针,sB成功的交赋予给sA自己的缓存区指针,完成之后,VB妈妈根据临时变量的指针,找到对应的字符串转换成Unicode赋给sA。至此完成了把sB赋给sA。但到了 CopyMemory sB,4这句,却无法把sA给sB,因为lTmp对应的缓存区已经释放了!所以得不到任何值。所以此代码只完成了一半任务。如果读到这你能读懂,聪明的你马上会想到如何更改第一段代码,一个小小的改动,就可以使第一段代码正确!就是把lTmp改成Sring型!让lTmp不是去存放一个临时字符串变量的缓冲区地址,而是去存放一个真正的字符串!lTmp字符串型与long型的最大区别就是:如果是long型,只是存放了一个临时的指针,而如果是String型,在CopyMemory执行完毕后,VB妈妈会把这个临时指针对应的字符串给lTmp,放在lTmp的缓冲区。第二段代码由于使用了ByVal VarPtr,所以VB妈妈不再进行UA转换,就没有临时变量,直接传送真实变量的地址。

下面是一个表,有助于你理解(这个表来源于网络,变量名与本文不符,但含义一致!)

如果你真的看懂了我所说的一切。如果你的头脑够灵活。你会继续思考,也会有第七感指引你:是不是还要明确一些东西?

我也不知道是怎么想到的,反正我就是意识到了:实际上第一段代码交换的是缓冲区的的字符串(变量sA、sB中存放的缓冲区的指针没有改变),而第二段代码交换的是变量sA、sB中存放的缓冲区的指针。两个都达到了交换的目的,但结果完全不同!

要验证这个还需要说明一个问题:就是VB中一个字符串变量定义完成时,在赋值之前,也就是分配内存空间之前,它是没有缓冲区的!也就是字符串变量在确定大小之前里边没有存放缓冲区指针!

Sub SwapPtr(sA As String,sB As String)

Dim lTmp As String

Dim lngBefore As Long

Dim lngAfter As Long

lngBefore = StrPtr(lTmp)

CopyMemory lTmp,4

lngAfter = StrPtr(lTmp)

MsgBox lngBefore & "|" & lngAfter

End Sub

调用一下这段代码,会发现lngBefore的值是0.

Sub SwapPtr(sA As String,sB As String)

Dim lTmp As String

Dim lngBefore As Long

Dim lngAfter As Long

CopyMemory lTmp,4

lngBefore = StrPtr(lTmp)

CopyMemory sA,4

lngAfter = StrPtr(lTmp)

MsgBox lngBefore & "|" & lngAfter

End Sub

调用一个这个,就是把lngBefore = StrPtr(lTmp)换了一下位置。这回lngBefore不是0了。这两个例子说明了上面的结果,同时也说明另一个问题:调用API时如果有字符串参数,如果不初始化, VB会自动初始化!但是API无法得知它的大小,初始化也不是API完成的,所以就有内存访问保护的风险!(长度不足!),所以尽量先初始化字符串参数再调用!

另外,很明显可以看出,在一个字符串变量从初始化到释放,它的缓冲区是不变的!lngBefore 永远等于lngAfter。

有了以上的基础,我们就可以用两段代码验证:“实际上第一段代码交换的是缓冲区的的字符串(变量sA、sB中存放的缓冲区的指针没有改变),而第二段代码交换的是变量sA、sB中存放的缓冲区的指针”

第一段验证代码:

Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any,Source As Any,ByVal Length As Long)

Private Sub Command1_Click()

'用指针的做法SwapPtr

Dim a As String

Dim b As String

Dim lngBeforeA As Long

Dim lngBeforeB As Long

Dim lngAfterA As Long

Dim lngAfterB As Long

a = "a"

b = "b"

lngBeforeA = StrPtr(a)

lngBeforeB = StrPtr(b)

SwapPtr a,b

lngAfterA = StrPtr(a)

lngAfterB = StrPtr(b)

MsgBox "a的值:" & a & "。b的值:" & b & Chr(10) & _

"a之前存放的指针" & lngBeforeA & "a之后存放的指针" & lngAfterA & Chr(10) & _

"b之前存放的指针" & lngBeforeB & "b之后存放的指针" & lngAfterB

End Sub

Sub SwapPtr(sA As String,sB As String)

Dim lTmp As String

CopyMemory lTmp,4

End Sub

结果如图:

分析:a,b的值已经交换,但a,b的缓冲区指针并没有改变,说明a,b是交换的缓冲区内容。

第二段验证代码:

Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any,4

End Sub

结果如图:

分析:a,b的结果交换,字符串变量a,b所存放的缓冲区指针也交换,说明这段代码交换的是a,b变量中存放的缓冲区指针,达到交换内容的效果。

好了,写了一上午。。。就写到这了,我感觉自己说的也不是太明白。但是说的都是关键。还需要自己努力,多看几遍,多思考。有不懂的可以留言!有指正批评的欢迎留言!

参考文章:http://www.m5home.com/bbs/thread-2362-1-1.html

http://blog.csdn.net/slowgrace/archive/2009/09/14/4549926.aspx

http://www.cnblogs.com/xw885/archive/2005/11/22/105264.html

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读