本文主要是介绍在VB6中用CopyMemory拷贝字符串的种种猫腻(四),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
版权声明:可以任意转载,转载时请务必以超链接形式标明如下文章原始出处和作者信息及本声明
作者:xixi
出处:http://blog.csdn.net/slowgrace/archive/2009/09/14/4550530.aspx
本文来自此帖的冗长讨论,感谢Tiger_Zhao的全程指点和陈辉、阿勇、马云剑等很多朋友的热心参与。本文其他部分在:(一)、(二)、(三)。
第四节 如何用CopyMemory正确的拷贝字符串
分析了这么多有问题的代码,我们来看看如何用CopyMemory正确的拷贝字符
串。假设String1 = “我有点Slow”
(1) 最简单的不会惹麻烦的方法是直接传地址。像下面这样
String2 = String$(Len(String1), 0) '14 bytes,和String1一样大小就OK了
CopyMemory ByVal StrPtr(String2), ByVal StrPtr(String1), LenB(String1)
这种方法由于不涉及UA/AU转换,要拷贝的就是Unicode字符串本身,所以字节数就取LenB(String1)就可以了,而String2的初始长度只要够接受String1的所有字符就行,所以就取Len(String1)。注意:1个是LenB,1个是Len,别弄混了。
(2) 或者你不嫌VB妈妈烦,像下面这样做
lngALen = LenB(StrConv(String1, vbFromUnicode))
String2 = String$(lngALen, 0)
CopyMemory ByVal String2, ByVal String1, lngALen
这里要拷贝的字节数和String2的初始化字符数都是用的lngALen,也即String1对应的ANSI字符串的字节数。这是因为传给CopyMemory的实际上是ANSI字符串临时变量_tmp1,所以要拷贝的字节数当然要按这个算;另一方面,String2初始成lngALen个0后(每个0字符占两个字节),它对应的ANSI字符串的字节数(每个0字符缩减为1个字节)就是lngALen,这样可以确保转换后的ANSI字符串_tmp2可以接收完整的_tmp1的字符串。
另外,要注意,在这种用法里,String2的初始长度不必等于它的最终长度。因为String2的字符串缓冲区在API函数调用完毕后要重新分配的,所以string2的初始长度只由_tmp1的LenB长度决定。附上完整的代码:
可以看到,推荐的方法有个共同的特征,就是目标地址和源地址的对称性。如果传Long就两者都传Long,如果传字符串就两者都传字符串,这样才能保证得到正确的结果;如果一个地址传Long,一个地址传字符串,那就会出问题,原因嘛,其实也就是因为VB妈妈对一个做了UA/AU转换,对另一个没做,导致互相对不上号,就要多加额外的语句来进行修正。
另外要注意正确处理好如下两个数字:
(1)CopyMemory的第3个参数byteLen。根据源地址参数是否涉及UA/AU转换而定,涉及的话就取对应的ANSI字符串的字节数,不涉及的话就直接LenB(String1)
(2)String2的初始字符数(注意是字符数不是字节数)。根据目标地址参数是否涉及UA/AU转换而定,涉及的话就保证对应的ANSI字符串字节数等于byteLen,不涉及的话就保证Unicode字符串字节数等于byteLen。
第五节 练习:这些回复你都能读懂么?
除了( 二)、( 三)详细讨论过的,还有许多朋友在 这个帖子里给出了自己的修正代码,不妨看一遍当做练习巩固一下你对本文的学习成果。综述如下:
8楼,正确。
这个实际是对0楼代码的直接修正。先扩充String2的长度以确保对应的_tmp2长度足够;最后再做UA转换以抵消VB多做的一次AU转换。
9楼,正确。
不经过UA/AU转换,直接拷贝字符串缓冲区。
12楼,正确。
不经过UA/AU转换,直接拷贝字符串缓冲区。
15楼(1),正确。
目的和源同时做UA/AU转换,直接拷贝字符串缓冲区。
15楼(2),正确。
不经过UA/AU转换,直接拷贝字符串缓冲区。
22楼,错误。
同10楼。见248楼解释。
23楼,错误。
同10楼。见248楼解释。
38楼,正确。
直接传递字符串参数要得到正确结果,很重要的一点是要算对字节数。坏上帝专门写了个函数来算ASCI字符串所占的字节数,所以能够得到正确的结果。不过的话,我觉得这个GetStringLength函数完全可以简化为这一条语句:LenB(StrConv("我有点slow", vbFromUnicode))
46楼,正确。
同15楼第一种方法。
50楼(1),正确。
同8楼。
50楼(2),正确。
同15楼第一种方法。(但解释不完全准确:)
57楼,正确。
这个其实同15楼第一种方法,但是更通用。在直接传字符串参数的时候,要用ANSI字符串来计算字节。
58楼,不完全正确。
String2的初始化长度取为Len(String1)就可以了。
62楼,正确。
用ByVal VarPtr(ByVal String1),呵呵,真够绕的。
129楼,错误。
拷贝字符串缓冲区指针会导致重复释放内存。见134,141楼解释。
165楼,正确。同9楼。
但使用了Len和LenB函数,使得代码更通用了。
231楼,错误。
拷贝字符串缓冲区指针会导致重复释放内存和内存泄露。见134,141楼解释。
237楼,错误。
拷贝字符串缓冲区指针会导致重复释放内存和内存泄露。见134,141楼解释。
第六节 补充提问
6.1 如何查看内存
其实要熟悉CopyMemory函数的话,猜来猜去不如直接看内存。怎样看内存呢?可以把目标地址的内存CopyMemory 到数组中,然后逐个字节 Hex。分析内存经常需要这样做。
另外还可以用一些辅助调试工具。比如OllyDbg:RING3下的调试工具,可用于实时浏览各变量值或者寄存器的值。再比如:Ida和冰刃等静态反汇编的工具可以帮助你了解高级语言的实际执行动作。
6.2 在VB6中调用API使用字符串参数时有可能避开UA/AU转换么
VB调用API,总是要经过一个中介,msvbvm60.dll中的DllFunctionCall,这是UA/AU转换的始作俑者。如果利用自己定义的TLB绕过DllFunctionCall,字符串就不会进行UA/AU转换了。
6.3 所有的API函数都把字符串参数当做ANSI字符串处理么
答案是否。有些API函数是,有些不是。对于期待Unicode字符串的API函数(成为W版API),我们直接通过VB调用传给它字符串参数就会出问题。因为VB不会管API函数期待的是啥,都会一股脑地给转成ANSI字符串才传。所以,对于不同类型的API我们需要区别对待,详见 这篇博文。
第七节 全文摘要
在VB6中要使用CopyMemory函数正确的拷贝字符串有点啰嗦。本文通过分析若干段经典代码,介绍了这其中涉及的各方面的知识,包括:
(1)VB6对字符串参数的自动UA/AU转换机制
(2)关于VB中的字符串:BSTR结构、StrPtr和VtrPtr函数的意义、字符串内存的初始化、查看字符串编码的方法、VB6释放字符串变量的方法等。
(3)关于CopyMemory函数;它的各种不同形式参数的含义、它如何自动处理覆盖、VB6调用CopyMemory后如何确定String2的长度等。
(4)其他内容:大端序、小端序、ANSI编码的内存结构、栈、关于VB崩溃的处理、查看内存的方法、用Tlb避开UA/AU转换的方法等。
附:经典代码段索引
第一节:详细图示、“表”示and“文字”示VB6对字符串参数的自动UA/AU转换机制。
第三节:例1:非对称的参数形式导致非对称的UA/AU转换,之后初始化字节数不够导致结果被截短;例2:非对称的参数形式导致非对称的UA/AU转换,得到“浮肿”的结果;例3:直接用String1传参数带来的危险后果。例4,本该传值却传了地址之后的后果;例5,中英文混杂的字符串如何算字节数;例6,危险的替换指针法。
第四节:正确方法剖析。
第五节:超多其他代码的简析。
全文完
这篇关于在VB6中用CopyMemory拷贝字符串的种种猫腻(四)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!