linux网络编程 copymemory,在VB6中用CopyMemory拷贝字符串的种种猫腻(一)

'正确的ByVal String的用法

OptionExplicit

ConstSTR_E ="PowerVB"

PrivateString1AsString

PrivateString2AsString

PrivatepString1AsLong

Subtest7()

DimString1AsString

DimString2AsString

'    Dim _tmp1 As String, _tmp2 As String

String1 = "PowerVB"'14 bytes

String2 = String$(7, 0)'14 bytes

CopyMemory ByValString2,ByValString1, 7

'    _tmp1 = StrConv(String1, vbFromUnicode) '7 bytes

'    _tmp2 = StrConv(String2, vbFromUnicode) '7 bytes

'    CopyMemory ByVal _tmp2, ByVal _tmp1, 7

Debug.Print String2

EndSub

10661965_1.jpg 如上图所示,当我们在VB中调用CopyMemory ByVal String2, ByVal String1, 7的时候发生了如下事情:

①首先VB妈妈帮我们对String1和String2自动做了UA转换,也就是相当于做了如下事情:

Dim_tmp1AsString, _tmp2AsString

_tmp1 = StrConv(String1, vbFromUnicode) '7 bytes

_tmp2 = StrConv(String2, vbFromUnicode) '7 bytes

也就是说,两个14字节的Unicode字符串现在被存在两个7字节的ANSI字符串里了。

②然后CopyMemory函数就做实际的拷贝动作。注意,这时CopyMemory得到的参数不是String1, String2了,而是VB妈妈传给它的_tmp1, _tmp2了。所以,实际上,CopyMemory同学是在这么干活:CopyMemory ByVal _tmp2, ByVal _tmp1, 7。也就是,从_tmp1的缓冲区拷贝7个字节到_tmp2的缓冲区。

③CopyMemory同学干完活,VB妈妈又细心地做善后工作。它把_tmp2的内容再转成14字节的Unicode字符串,并把它给String2。

PS:

(1) 文字中带圈标号1与图上的1是一一对应的。

(2) 注意①和③是VB自动进行的,和CopyMemory函数无关。也就是VB只要看到API函数调用中涉及到字符串参数,就会自动做这种转换!

看完上面的例子,也许你就会对VB妈妈这种细致体贴的劲头有点体会了。但是正如现实生活中妈妈的过多干涉会给我们带来困扰一样,VB妈妈的这种体贴有时也会带来让人哭笑不得的效果。

第二节 基础知识

在展示VB妈妈的各种“杰作”之前,我们先来准备一些基础知识。

2.1 VB中字符串的存储结构

当你在VB里声明了一个String型的变

量,比如:Dim str1 As

String。这个Str1本身其实是一个4字节的Long型,里面存的是一个指针,指向的是实际字符串的缓冲区开始地址,这个开始地址前面4字节里存放

的是这个缓冲区的长度,单位为字节。也就是,VB里的String其实是像下面这样定义的:

TypeString

dwSize as long  '后面实际数据的长度'

pData() as Integer'实际数据,每一个word就是一个字符,16位'

wEnd as Integer'字符串结束点\0\0,一个Unicode字符占双字节,不计入长度

end type

所以,VarPtr取到的地址是字符串变量的地址,也就是字符串变量指针,也就是存放"指向pData这个地址的

指针"的变量的地址;而StrPtr取到的值就是指向pData地址的指针,也就是字符串缓冲区指针。所以,有时候人们会说,同一个字符串有两个指针,一

个是字符串变量指针、另一个是字符串缓冲区指针。看下面的示例,可以更好的理解以上的说法:

OptionExplicit

'From Myjian

'http://topic.csdn.net/u/20090901/09/dddf35aa-7838-4415-85b2-222358422d81_2.html 187楼

PrivateDeclareSubCopyMemoryLib"kernel32.dll"Alias"RtlMoveMemory"( _

ByValDestinationAsLong, _

ByValSourceAsLong, _

ByValLengthAsLong)

SubTestBstr()

Dimstr1AsString, JAsLong, KAsLong

str1 = "IamSlow慢"

Debug.Print VarPtr(str1)                            '得到变量本身的地址

CallCopyMemory(VarPtr(J), VarPtr(str1), 4)'取得str1里面保存的指针,与StrPtr一样

Debug.Print J, StrPtr(str1)

K = LenPtr(J)                                       '得到字符串的长度,实际字节值

Debug.Print K, Len(str1), LenB(str1)

Debug.Print GetBSTRFromPtr(J)                       '根据这个指针得到字符串

Debug.Print GetBSTRFromPtr(StrPtr(str1))

EndSub

PrivateFunctionGetBSTRFromPtr(ByVallpStrAsLong)AsString

'从指针得到BSTR字符串

DimInStrLenAsLong, OutStrArr()AsByte

InStrLen = LenPtr(lpStr)        '得到输入字符串的长度

ReDimOutStrArr(InStrLen - 1)

CallCopyMemory(VarPtr(OutStrArr(0)), lpStr, InStrLen)

GetBSTRFromPtr = OutStrArr

EndFunction

PrivateFunctionLenPtr(ByVallpStrAsLong)AsLong

'根据指针取BSTR长度

DimInStrLenAsLong

IflpStr = 0ThenExitFunction

CopyMemory VarPtr(InStrLen), lpStr - 4, 4 '得到输入字符串的长度

LenPtr = InStrLen

EndFunction

注意,上面的LenPtr函数,是直接通过从字符串缓冲区的长度前缀中拷贝内存得到的。这其实是BSTR指针的特点,你只要保证传入的指针是BSTR指针就可以这样得到字符串的长度。

BSTR是COM中的一种字符串标准,与普通字符串的最大不同在于有长度前缀,所以可以包含NULL在内的字符串。而如果没有长度前缀,字符串中有NULL就会被认为是结束了,从而截断。VB中的字符串就是BSTR类型的。下面这个丑陋但清晰的图说明了一切。

10661965_2.gif

我们还可以用以下的代码来验证上面的说法:

SubtestNull()

Dimstr1AsString

str1 = "aa"& Chr(0) &"bb"

Debug.Print str1, Len(str1), LenB(str1)

MsgBox str1

EndSub

可以看出,VB中的字符串中间可以含有NULL字符,但是MsgBox这样的函数由于是封装的API函数MessageBox,所以它会按照C字符串的标准来解释字符串长度,因此会把aa以后的字符截掉。

2.2 CopyMemory函数

下面我们来熟悉一下本文重点讨论的这个函数。

DeclareSubCopyMemoryLib"kernel32"Alias"RtlMoveMemory"_

(pDest AsAny, pSourceAsAny,ByValbyteLenAsLong)

这个函数的功能是从pSource拷贝byteLen个字节的数据到pDest,其中源地址和目标地址都是声明为Any类型。下面是CopyMemory对不同形式参数的理解:

(1) 传一个变量给pSource,那么源地址就是变量所在的地址

(2) 以ByVal形式传一个变量给pSource,那么源地址就是变量的值

(3) 字符串变量的值是个指针,指向字符串缓冲区的地址,也就是StrPtr(String1)。因此,以ByVal形式传一个字符串变量给pSource,那么源地址就是字符串变量的值,也就是字符串缓冲区的地址。

下表总结了几种常见的传参数给CopyMemory的形式:

10661965_3.jpg

注:

(1)取到的内容根据byteLen实际规定的字节数的多少,可能有所不同,这里只是个大概。

(2)带高亮的两行,VB对字符串参数做了自动的UA转换,所以实际的CopyMemory动作针对的是由String1转换得到的ANSI字符串_tmp1而进行的。

(3)字节数那

一列给出了要取到有效的数据byteLen参数可以使用的数字范围。简单的说,如果pSource的参数是字符串类型的话,那么byteLen的字节数要

取为String1对应的ANSI字符串的长度。要理解这个也容易,你只要记住CopyMemory这时候实际上是对ANSI字符串做操作就可以了。而如

果不发生字符串转换的话,像表里第4行,那么你就要拷贝String1的LebB长度。这也好理解,不发生转换的话,CopyMemory实际上是在直接

拷贝Unicode字符串的内容啊。

继续学习后续内容前,不妨做以下练习,以确认你已经掌握本节内容。SubTest2_Ptr()

string1 = STR_E

'结果:StrPtr((string1)

'把VarPtr(String1)的值作为地址,拷这个地址里的值出来:)

CopyMemory pString1, ByValVarPtr(string1), 4

Debug.Print pString1, StrPtr(string1), VarPtr(string1)

'结果:VarPtr(String1)

'把VarPtr(String1)这个变量的值拷出来

CopyMemory pString1, VarPtr(string1), 4

Debug.Print pString1, StrPtr(string1), VarPtr(string1)

'结果:StrPtr(_tmp1)

CopyMemory pString1, string1, 4

Debug.Print pString1, StrPtr(string1), VarPtr(string1)

'结果:"ewoP"的ANSI编码

'从内部临时ANSI变量的字符串缓冲区取4个字节出来

'"Powe"是50-6F-77-65,取到的pString1里是(65776F50),正好倒过来

'因为Long型在书写时是大端在前的

CopyMemory pString1, ByValstring1, 4

Debug.Print pString1, StrPtr(string1), VarPtr(string1)

Debug.Print Hex(pString1)

EndSub

2.3 大端序和小端序

Test2_Ptr里的结果你都猜的正确么?我猜除了最后一个,应该都正确,呵呵。学习完以上的基础知识,下面这个语句的基本意思不难推测出来:

'从内部临时ANSI变量的字符串缓冲区取头4个字节出来

CopyMemory pString1, ByValString1, 4

但是有趣的是,头4个字节"Powe"对应的编码是50-6F-77-65,可是取到的pString1里是(65776F50),正好倒过来。这是为什么呢?看下面的解释:

(1)字符串的数据相当于Byte数组,它的字符是放在一个连续的内存块里的。第一个字符地址最低,最后一个字符最高。

(2)

当用Long变量去拷贝字符串的部分内容的时候,Long的高字节对应它取到的最后一个字符,低字节则对应第一个字符。而在数字世界里,我们是把高字节写

在左边、低字节写在右边的。所以我们从Long里去观察取到的字符,看起来是最后一个字符在左边、第一个字符在右边,好像倒了。

下面的例子可以帮助你更好的理解这一点:

'测试Long在内存的存储顺序和拷贝顺序

Subtest11()

DimLong1AsLong

DimLong2AsLong

DimiAsLong

Long1 = &H1020304

Debug.Print Hex(Long1)

Fori = 1To4

CopyMemory Long2, Long1, i

Debug.Print Hex(Long2)

Nexti

EndSub

输出

1020304

4

304

20304

1020304

'测试String在内存的存储顺序和拷贝顺序

Subtest12()

DimString1AsString

DimString2AsString

DimiAsLong

String1 = "1234"

String2 = String$(4, 0)

Debug.Print String1

Fori = 1To4

CopyMemory ByValString2,ByValString1, i

Debug.Print String2

Nexti

EndSub

输出

1234

1

12

123

1234

这里要补充一些关于字节序的知识。Big Endian和Little Endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian,译作大端序。还是将49写在前面,就是little endian,译作小端序。

“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。

大端序指的是:从最大的一端开始存储(从低地址存起),MSB的地址最低。

小端序指的是:从最小的一端开始存储(从低地址存起),MSB的地址最高。

像我们上面测试的Long,它的最高位是1,最低位是4,从拷贝出来的结果可以看出来4在最低位,也就是从小端开始存储,所以我们说它是小端序的。实际上Intel处理器都是小端序的。

而在Big-endian处理器(如苹果

Macintosh电脑)上建立的Unicode文件中的文字位元组(存放单位)排列顺序,与在Intel处理器上建立的文件的文字位元组排列顺序相反。

最重要的位元组(MSB)拥有最低的地址,且会先储存文字中较大的一端。为使这类电脑的用户能够存取你的文件,可选择Unicode

big-endian格式。

2.4 如何传参数会被VB6当做字符串?

Q:VB6根据什么判断要传给CopyMemory的参数是字符串,因而会触发自动的UA /AU转换?以下这些传法,哪种会转,哪种不会转?

(1)ByVal String2

(2)ByVal StrPtr(String1)

(3)ByRef String1

(4)ByVal VarPtr(String1)

A:

(1)ByVal String2:字符串参数,自动转换。

(2)ByVal StrPtr(String1):指针,不转换。

(3)ByRef String1:编译错误,去掉 ByRef 可以通过编译,也会引起UA/AU转换。但其实 Any 类型的参数不支持这种用法,会导致无法预期的结果甚至程序崩溃(见后续讨论)。

(4)ByVal VarPtr(String1):指针的指针,不转换。但是这其实是变量 String1 所在的位置,不当操作也会导致无法预期的结果甚至程序崩溃(见后续讨论)。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值