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

摘要:
首先,我认为有必要花精力研究VB调用API字符串的各种技巧。我的问题从调用内存API时参数的ByValVarPtr+变量与直接写入变量之间的差异开始。一旦了解了VB字符串的结构,关键点就来了。VB中的所有字符串都用Unicode表示,而API中的字符串则用ASNI表示。因此,当调用API涉及字符串参数时,VB母亲会自动为程序员完成从Unicode到ASNI的转换。根据共同的理解:VB妈妈首先发现sA是一个字符串参数,并首先执行了UA转换。

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

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

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

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

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

 

在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, sB As String)

  Dim lTmp As Long

  CopyMemory lTmp, ByVal VarPtr(sA), 4

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

  CopyMemory ByVal VarPtr(sB), lTmp, 4

End Sub

 

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

 

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

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

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

 

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

我也不知道是怎么想到的,反正我就是意识到了:实际上第一段代码交换的是缓冲区的的字符串(变量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, sA, 4

  CopyMemory sA, sB, 4

  CopyMemory sB, 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, sA, 4

  lngBefore = StrPtr(lTmp)

  CopyMemory sA, sB, 4

  CopyMemory sB, lTmp, 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, sA, 4

  CopyMemory sA, sB, 4

  CopyMemory sB, lTmp, 4

End Sub

 

结果如图:

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

 

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

 

第二段验证代码:

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 Long

  CopyMemory lTmp, ByVal VarPtr(sA), 4

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

  CopyMemory ByVal VarPtr(sB), lTmp, 4

End Sub

 

结果如图:

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

 

分析: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

 

免责声明:文章转载自《澄清VB调用API时字符串参数的困惑》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇第3章 从Flux到ReduxJavaDump 规格严格下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

chapter11:认识与学习bash之(1)认识shell与shell的变量功能

  使用终端的命令行就是通过bash 环境变量来处理的,bash还包括变量的设置与使用,bash操作环境的构建,数据流的重定向和管道等。 一,认识bash这个shell 1.什么是shell   我们一般通过shell来和内核进行通信,因为内核是要保护的。也就是说通过shell将我们的命令与内核通信,好让崔可以控制硬件来完成工作。   其实shell只是提...

Freemarker常用技巧(二)

1 list、break指令<#list sequence as item>  ...</#list>tem_index:当前变量的索引值.item_has_next:是否存在下一个对象.<#list ["星期一","星期二","星期三","星期四","星期五","星期六"] as x>${x_index + 1}.${...

【转】Asp.Net MVC及Web API框架配置会碰到的几个问题及解决方案

前言 刚开始创建MVC与Web API的混合项目时,碰到好多问题,今天拿出来跟大家一起分享下。有朋友私信我问项目的分层及文件夹结构在我的第一篇博客中没说清楚,那么接下来我就准备从这些文件怎么分文件夹说起。问题大概有以下几点: 1、项目层的文件夹结构       2、解决MVC的Controller和Web API的Controller类名不能相同的问题  ...

Python内置进制转换函数(实现16进制和ASCII转换)

在进行wireshark抓包时你会发现底端窗口报文内容左边是十六进制数字,右边是每两个十六进制转换的ASCII字符,这里使用Python代码实现一个十六进制和ASCII的转换方法。 hex() 转换一个整数对象为十六进制的字符串 >>> hex(16) '0x10' >>> hex(18) '0x12' >>...

postman-变量/环境/过滤等

之前虽然自己也有用postman来测试api,但都只是最简单输入url,发送,查看结果,大部分时候跟使用浏览器差不多,偶然在简书首页看到一篇 API开发神器-Postman , 深感还是得对自己使用的工具多琢磨一下,兴许你就发现了宝贝,大大加快开发测试速度了; 这里没有打算写全面的教程,只对我之前没了解的环境设置/变量使用以及对返回结果进行过滤等操作进行记...

SQL中declare变量的作用域(续)一些问题

在上次讨论了declare变量的作用域以后我们继续深入谈论一下,准确的说是我有些疑惑想跟大家讨论,有高手明白的话指点一下。 关于作用域的谈论:http://www.cnblogs.com/breezeli/archive/2010/04/16/1713308.html 这个问题不太好解释,大家看一段代码,在循环中定义表变量。 注意 代码使用Northw...