日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

在VB6中用CopyMemory拷貝字符串的種種貓膩(一)

 nxhujiee 2011-04-05
  1. '正確的ByVal String的用法  
  2. Option Explicit  
  3.   
  4. Const STR_E = "PowerVB"  
  5. Private String1 As String  
  6. Private String2 As String  
  7. Private pString1 As Long  
  8.   
  9. Sub test7()  
  10.     Dim String1 As String  
  11.     Dim String2 As String  
  12. '    Dim _tmp1 As String, _tmp2 As String  
  13.       
  14.     String1 = "PowerVB" '14 bytes  
  15.     String2 = String$(7, 0) '14 bytes  
  16.       
  17.     CopyMemory ByVal String2, ByVal String1, 7  
  18.       
  19. '    _tmp1 = StrConv(String1, vbFromUnicode) '7 bytes  
  20. '    _tmp2 = StrConv(String2, vbFromUnicode) '7 bytes  
  21. '    CopyMemory ByVal _tmp2, ByVal _tmp1, 7  
  22.       
  23.     Debug.Print String2  
  24. End Sub  
clip_image002 如上圖所示,當(dāng)我們在VB中調(diào)用CopyMemory ByVal String2, ByVal String1, 7的時候發(fā)生了如下事情:
①首先VB媽媽幫我們對String1和String2自動做了UA轉(zhuǎn)換,也就是相當(dāng)于做了如下事情:
  1. Dim _tmp1 As String, _tmp2 As String  
  2.   
  3. _tmp1 = StrConv(String1, vbFromUnicode) '7 bytes  
  4. _tmp2 = StrConv(String2, vbFromUnicode) '7 bytes  
也就是說,兩個14字節(jié)的Unicode字符串現(xiàn)在被存在兩個7字節(jié)的ANSI字符串里了。
②然后CopyMemory函數(shù)就做實際的拷貝動作。注意,這時CopyMemory得到的參數(shù)不是String1, String2了,而是VB媽媽傳給它的_tmp1, _tmp2了。所以,實際上,CopyMemory同學(xué)是在這么干活:CopyMemory ByVal _tmp2, ByVal _tmp1, 7。也就是,從_tmp1的緩沖區(qū)拷貝7個字節(jié)到_tmp2的緩沖區(qū)。
③CopyMemory同學(xué)干完活,VB媽媽又細(xì)心地做善后工作。它把_tmp2的內(nèi)容再轉(zhuǎn)成14字節(jié)的Unicode字符串,并把它給String2。
PS:
(1) 文字中帶圈標(biāo)號1與圖上的1是一一對應(yīng)的。
(2) 注意①和③VB自動進(jìn)行的,和CopyMemory函數(shù)無關(guān)。也就是VB只要看到API函數(shù)調(diào)用中涉及到字符串參數(shù),就會自動做這種轉(zhuǎn)換!
 
看完上面的例子,也許你就會對VB媽媽這種細(xì)致體貼的勁頭有點體會了。但是正如現(xiàn)實生活中媽媽的過多干涉會給我們帶來困擾一樣,VB媽媽的這種體貼有時也會帶來讓人哭笑不得的效果。
 
第二節(jié) 基礎(chǔ)知識
在展示VB媽媽的各種“杰作”之前,我們先來準(zhǔn)備一些基礎(chǔ)知識。
2.1 VB中字符串的存儲結(jié)構(gòu)
當(dāng)你在VB里聲明了一個String型的變 量,比如:Dim str1 As String。這個Str1本身其實是一個4字節(jié)的Long型,里面存的是一個指針,指向的是實際字符串的緩沖區(qū)開始地址,這個開始地址前面4字節(jié)里存放 的是這個緩沖區(qū)的長度,單位為字節(jié)。也就是,VB里的String其實是像下面這樣定義的:
  1. Type String   
  2.     dwSize as long  '后面實際數(shù)據(jù)的長度'   
  3.     pData() as Integer '實際數(shù)據(jù),每一個word就是一個字符,16位'   
  4.     wEnd as Integer '字符串結(jié)束點\0\0,一個Unicode字符占雙字節(jié),不計入長度   
  5. end type  
所以,VarPtr取到的地址是字符串變量的地址,也就是字符串變量指針,也就是存放"指向pData這個地址的 指針"的變量的地址;而StrPtr取到的值就是指向pData地址的指針,也就是字符串緩沖區(qū)指針。所以,有時候人們會說,同一個字符串有兩個指針,一 個是字符串變量指針、另一個是字符串緩沖區(qū)指針。看下面的示例,可以更好的理解以上的說法:
  1. Option Explicit  
  2.   
  3. 'From Myjian  
  4. 'http://topic.csdn.net/u/20090901/09/dddf35aa-7838-4415-85b2-222358422d81_2.html 187樓  
  5.   
  6. Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" ( _  
  7.      ByVal Destination As Long, _  
  8.      ByVal Source As Long, _  
  9.      ByVal Length As Long)  
  10.   
  11. Sub TestBstr()  
  12.     Dim str1 As String, J As Long, K As Long  
  13.       
  14.     str1 = "IamSlow慢"  
  15.     Debug.Print VarPtr(str1)                            '得到變量本身的地址  
  16.       
  17.     Call CopyMemory(VarPtr(J), VarPtr(str1), 4)         '取得str1里面保存的指針,與StrPtr一樣  
  18.     Debug.Print J, StrPtr(str1)  
  19.       
  20.     K = LenPtr(J)                                       '得到字符串的長度,實際字節(jié)值  
  21.     Debug.Print K, Len(str1), LenB(str1)  
  22.       
  23.     Debug.Print GetBSTRFromPtr(J)                       '根據(jù)這個指針得到字符串  
  24.     Debug.Print GetBSTRFromPtr(StrPtr(str1))  
  25. End Sub  
  26.   
  27. Private Function GetBSTRFromPtr(ByVal lpStr As LongAs String  
  28.     '從指針得到BSTR字符串  
  29.     Dim InStrLen As Long, OutStrArr() As Byte  
  30.       
  31.     InStrLen = LenPtr(lpStr)        '得到輸入字符串的長度  
  32.     ReDim OutStrArr(InStrLen - 1)  
  33.     Call CopyMemory(VarPtr(OutStrArr(0)), lpStr, InStrLen)  
  34.       
  35.     GetBSTRFromPtr = OutStrArr  
  36. End Function  
  37.   
  38. Private Function LenPtr(ByVal lpStr As LongAs Long  
  39.     '根據(jù)指針取BSTR長度  
  40.     Dim InStrLen As Long  
  41.       
  42.     If lpStr = 0 Then Exit Function  
  43.     CopyMemory VarPtr(InStrLen), lpStr - 4, 4 '得到輸入字符串的長度  
  44.     LenPtr = InStrLen  
  45. End Function  
注意,上面的LenPtr函數(shù),是直接通過從字符串緩沖區(qū)的長度前綴中拷貝內(nèi)存得到的。這其實是BSTR指針的特點,你只要保證傳入的指針是BSTR指針就可以這樣得到字符串的長度。
 
BSTR是COM中的一種字符串標(biāo)準(zhǔn),與普通字符串的最大不同在于有長度前綴,所以可以包含NULL在內(nèi)的字符串。而如果沒有長度前綴,字符串中有NULL就會被認(rèn)為是結(jié)束了,從而截斷。VB中的字符串就是BSTR類型的。下面這個丑陋但清晰的圖說明了一切。
clip_image004
我們還可以用以下的代碼來驗證上面的說法:
  1. Sub testNull()  
  2.     Dim str1 As String  
  3.     str1 = "aa" & Chr(0) & "bb"  
  4.     Debug.Print str1, Len(str1), LenB(str1)  
  5.     MsgBox str1  
  6. End Sub  
可以看出,VB中的字符串中間可以含有NULL字符,但是MsgBox這樣的函數(shù)由于是封裝的API函數(shù)MessageBox,所以它會按照C字符串的標(biāo)準(zhǔn)來解釋字符串長度,因此會把a(bǔ)a以后的字符截掉。
 
2.2 CopyMemory函數(shù)
下面我們來熟悉一下本文重點討論的這個函數(shù)。
  1. Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _  
  2.     (pDest As Any, pSource As Any, ByVal byteLen As Long)  
這個函數(shù)的功能是從pSource拷貝byteLen個字節(jié)的數(shù)據(jù)到pDest,其中源地址和目標(biāo)地址都是聲明為Any類型。下面是CopyMemory對不同形式參數(shù)的理解:
(1) 傳一個變量給pSource,那么源地址就是變量所在的地址
(2) 以ByVal形式傳一個變量給pSource,那么源地址就是變量的值
(3) 字符串變量的值是個指針,指向字符串緩沖區(qū)的地址,也就是StrPtr(String1)。因此,以ByVal形式傳一個字符串變量給pSource,那么源地址就是字符串變量的值,也就是字符串緩沖區(qū)的地址。
 
下表總結(jié)了幾種常見的傳參數(shù)給CopyMemory的形式:
表格-CopyMemory使用字符串參數(shù)的各種可能形式
注:
(1)取到的內(nèi)容根據(jù)byteLen實際規(guī)定的字節(jié)數(shù)的多少,可能有所不同,這里只是個大概。
(2)帶高亮的兩行,VB對字符串參數(shù)做了自動的UA轉(zhuǎn)換,所以實際的CopyMemory動作針對的是由String1轉(zhuǎn)換得到的ANSI字符串_tmp1而進(jìn)行的。
(3)字節(jié)數(shù)那 一列給出了要取到有效的數(shù)據(jù)byteLen參數(shù)可以使用的數(shù)字范圍。簡單的說,如果pSource的參數(shù)是字符串類型的話,那么byteLen的字節(jié)數(shù)要 取為String1對應(yīng)的ANSI字符串的長度。要理解這個也容易,你只要記住CopyMemory這時候?qū)嶋H上是對ANSI字符串做操作就可以了。而如 果不發(fā)生字符串轉(zhuǎn)換的話,像表里第4行,那么你就要拷貝String1的LebB長度。這也好理解,不發(fā)生轉(zhuǎn)換的話,CopyMemory實際上是在直接 拷貝Unicode字符串的內(nèi)容啊。
 
繼續(xù)學(xué)習(xí)后續(xù)內(nèi)容前,不妨做以下練習(xí),以確認(rèn)你已經(jīng)掌握本節(jié)內(nèi)容。
  1. Sub Test2_Ptr()  
  2.     string1 = STR_E  
  3.       
  4.     '結(jié)果:StrPtr((string1)  
  5.     '把VarPtr(String1)的值作為地址,拷這個地址里的值出來:)  
  6.     CopyMemory pString1, ByVal VarPtr(string1), 4  
  7.     Debug.Print pString1, StrPtr(string1), VarPtr(string1)  
  8.       
  9.     '結(jié)果:VarPtr(String1)  
  10.     '把VarPtr(String1)這個變量的值拷出來  
  11.     CopyMemory pString1, VarPtr(string1), 4  
  12.     Debug.Print pString1, StrPtr(string1), VarPtr(string1)  
  13.       
  14.     '結(jié)果:StrPtr(_tmp1)  
  15.     CopyMemory pString1, string1, 4  
  16.     Debug.Print pString1, StrPtr(string1), VarPtr(string1)  
  17.       
  18.     '結(jié)果:"ewoP"的ANSI編碼  
  19.     '從內(nèi)部臨時ANSI變量的字符串緩沖區(qū)取4個字節(jié)出來  
  20.     '"Powe"是50-6F-77-65,取到的pString1里是(65776F50),正好倒過來  
  21.     '因為Long型在書寫時是大端在前的  
  22.     CopyMemory pString1, ByVal string1, 4  
  23.     Debug.Print pString1, StrPtr(string1), VarPtr(string1)  
  24.     Debug.Print Hex(pString1)  
  25. End Sub  
2.3 大端序和小端序
Test2_Ptr里的結(jié)果你都猜的正確么?我猜除了最后一個,應(yīng)該都正確,呵呵。學(xué)習(xí)完以上的基礎(chǔ)知識,下面這個語句的基本意思不難推測出來:
  1. '從內(nèi)部臨時ANSI變量的字符串緩沖區(qū)取頭4個字節(jié)出來  
  2. CopyMemory pString1, ByVal String1, 4  
但是有趣的是,頭4個字節(jié)"Powe"對應(yīng)的編碼是50-6F-77-65,可是取到的pString1里是(65776F50),正好倒過來。這是為什么呢?看下面的解釋:
(1)字符串的數(shù)據(jù)相當(dāng)于Byte數(shù)組,它的字符是放在一個連續(xù)的內(nèi)存塊里的。第一個字符地址最低,最后一個字符最高。
(2) 當(dāng)用Long變量去拷貝字符串的部分內(nèi)容的時候,Long的高字節(jié)對應(yīng)它取到的最后一個字符,低字節(jié)則對應(yīng)第一個字符。而在數(shù)字世界里,我們是把高字節(jié)寫 在左邊、低字節(jié)寫在右邊的。所以我們從Long里去觀察取到的字符,看起來是最后一個字符在左邊、第一個字符在右邊,好像倒了。
 
下面的例子可以幫助你更好的理解這一點:
  1. '測試Long在內(nèi)存的存儲順序和拷貝順序  
  2. Sub test11()  
  3.     Dim Long1 As Long  
  4.     Dim Long2 As Long  
  5.     Dim i As Long  
  6.       
  7.     Long1 = &H1020304  
  8.     Debug.Print Hex(Long1)  
  9.     For i = 1 To 4  
  10.         CopyMemory Long2, Long1, i  
  11.         Debug.Print Hex(Long2)  
  12.     Next i  
  13. End Sub  
輸出
1020304
4
304
20304
1020304

 

  1. '測試String在內(nèi)存的存儲順序和拷貝順序  
  2. Sub test12()  
  3.     Dim String1 As String  
  4.     Dim String2 As String  
  5.     Dim i As Long  
  6.       
  7.     String1 = "1234"  
  8.     String2 = String$(4, 0)  
  9.     Debug.Print String1  
  10.     For i = 1 To 4  
  11.         CopyMemory ByVal String2, ByVal String1, i  
  12.         Debug.Print String2  
  13.     Next i  
  14. End Sub  

 

輸出
1234
1
12
123
1234
這里要補(bǔ)充一些關(guān)于字節(jié)序的知識。Big Endian和Little Endian是CPU處理多字節(jié)數(shù)的不同方式。例如“漢”字的Unicode編碼是6C49。那么寫到文件里時,究竟是將6C寫在前面,還是將49寫在前面?如果將6C寫在前面,就是big endian,譯作大端序。還是將49寫在前面,就是little endian,譯作小端序
 
“endian”這個詞出自《格列佛游記》。小人國的內(nèi)戰(zhàn)就源于吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開,由此曾發(fā)生過六次叛亂,其中一個皇帝送了命,另一個丟了王位。
大端序指的是:從最大的一端開始存儲(從低地址存起),MSB的地址最低。
小端序指的是:從最小的一端開始存儲(從低地址存起),MSB的地址最高。
 
像我們上面測試的Long,它的最高位是1,最低位是4,從拷貝出來的結(jié)果可以看出來4在最低位,也就是從小端開始存儲,所以我們說它是小端序的。實際上Intel處理器都是小端序的。
 
而在Big-endian處理器(如蘋果 Macintosh電腦)上建立的Unicode文件中的文字位元組(存放單位)排列順序,與在Intel處理器上建立的文件的文字位元組排列順序相反。 最重要的位元組(MSB)擁有最低的地址,且會先儲存文字中較大的一端。為使這類電腦的用戶能夠存取你的文件,可選擇Unicode big-endian格式。
 
2.4 如何傳參數(shù)會被VB6當(dāng)做字符串?
Q:VB6根據(jù)什么判斷要傳給CopyMemory的參數(shù)是字符串,因而會觸發(fā)自動的UA /AU轉(zhuǎn)換?以下這些傳法,哪種會轉(zhuǎn),哪種不會轉(zhuǎn)?
(1)ByVal String2
(2)ByVal StrPtr(String1)
(3)ByRef String1
(4)ByVal VarPtr(String1)
A
(1)ByVal String2:字符串參數(shù),自動轉(zhuǎn)換。
(2)ByVal StrPtr(String1):指針,不轉(zhuǎn)換。
(3)ByRef String1:編譯錯誤,去掉 ByRef 可以通過編譯,也會引起UA/AU轉(zhuǎn)換。但其實 Any 類型的參數(shù)不支持這種用法,會導(dǎo)致無法預(yù)期的結(jié)果甚至程序崩潰(見后續(xù)討論)。
(4)ByVal VarPtr(String1):指針的指針,不轉(zhuǎn)換。但是這其實是變量 String1 所在的位置,不當(dāng)操作也會導(dǎo)致無法預(yù)期的結(jié)果甚至程序崩潰(見后續(xù)討論)。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多