有關API字串--API programmer請看 (轉)

worldblog發表於2007-12-04
有關API字串--API programmer請看 (轉)[@more@] Programming with 作者:Steven Roman 摘自《Win32 API Programming with Visual Basic》。請聯絡:nuts@oreilly.com或 前言: 字串很容易把人搞糊塗,但是隻要關心一下細節其實還是可以做到心中有數的。主要的問題是術語“string”在Visual Basic中至少有兩種不同的使用方式。在Visual Basic什麼叫做string?Visual Basic文件中是這樣說的:由一組有次序的相鄰的字元組成的相對於數字來說是表達字元性質的一種資料型別。哈!好象打算說string型別的根本特徵是有限個的字元長度。對Visual Basic來說,所有的字元是以2位元組的Unicode形式表述的。(譯者:在Visual Basic裡處理字元是以Unicode格式的,不管所處的操作是可以以ANSI格式處理字元的還是以Unicode格式的WinNT,但Visual Basic也支援ANSI格式的字串傳送,例如一個需要接收ANSI字串的API時,Visual Basic會自動將其內部以Unicode格式表述的字串轉換為ANSI格式字串傳送。ANSI格式的字元是僅以一個位元組來表述的字元。)從另一方面來說,Visual Basic使用Unicode格式在字串中表述字元。例如,ASCII這樣表述字元h:&H68,而Unicode是這樣表述的:&H0068,同時在中則是這樣儲存:68 00。本來,字串“help”在記憶體中應該儲存為這樣: 00 68 00 65 00 6c 00 70 注意,由於字串在記憶體中是顛倒儲存的,所以,實際上“help“在記憶體是這樣儲存的: 68 00 65 00 6c 00 70 00 好了,我們現在知道在Visual Basic中字串類string並非我們原先想象的那樣。為了避免任何可能的誤解,我們把string型別理解為Unicode格式的字元陣列,而實際上它就是這樣子的。這也有助於我們區別ANSI格式的字元陣列。請仔細閱讀以下幾句話,因為這是理解string型別的關鍵:當我們寫下這幾句程式碼時: Dim str as String Str=”help” 本質上來說,我們其實並沒有定義了一個Unicode格式的字元陣列。我們其實定義了一個BSTR型別的變數。一個BSTR型別的變數就是一個指向以NULL結尾的,以四個位元組為保留字開頭的Unicode格式字元陣列的指標。 BSTR 型別: 事實上,在Visual Basic3升級到Visual Basic4的時候, 用Dim語句定義一個string已經經歷了一個根本性的變化。這種變化部份的起因是為了使string型別更好地相容Win32。為了對比一下(也為了顯示我們現在是多麼地幸運),圖6-1展示了在Visual Basic3時叫做HLSTR(High-Level String)的Visual Basic string型別資料格式。 Figure 6-1. The high-level string format (HLSTR) used by VB3 這種複雜的HLSTR格式的資料其實是一個指向string descriptor的指標。String descriptor共佔用4個位元組。2位元組描述字串長度,另外2位元組儲存指向ANSI格式字元陣列的指標。在呼叫Win32 API的時候,這種資料型別對員來說簡直就是個惡夢。從Visual Basic 4開始,string型別就不同了。新的資料型別叫做BSTR字串。請參考圖6-2: Figure 6-2. A BSTR 這種資料型別事實上是以OLE 2.0規範定義的,也就是說,它是’s 規範的一部分。 BSTR字串有以下幾個要點要強調一下: 1.一個BSTR字串變數實際上是一個指標變數。它佔用32bit即4個位元組,就像其它的指標一樣。而且,它指向一個Unicode格式的字元陣列。但是,我們不能把字串與BSTR字串等同起來。我們必須用它自己確切的名字――“BSTR“。 2.一個BSTR字串變數指向的字串陣列必須由4個位元組的保留字開始(儲存字串陣列的位元組數而不是字元數),由2個空字元結束。 3.由於空字元在Unicode格式的字串中的任何位置都有可能出現,所以,以空字元宣告一個Unicode格式的字串的結束並不合適。因此,在4個保留字中儲存字串的長度是至關重要的。 4.我們再強調一下,BSTR字串指標實際指向的是Unicode格式的字元陣列的首地址,而不是開頭的4個位元組。接下來我們就會看到,在這兒這樣不厭其煩地強調BSTR字串變數的特徵是為了與馬上就要解釋的VC++中的string型別作個比較。 5.圖示中的4個位元組的length記錄的是字元陣列的位元組數(注意,不是字元數),包括結尾的空位元組。因為陣列是Unicode格式的,所以實際字元是length的一半。在這兒強調一下,一個Unicode格式的空字元其實是佔用2個位元組的空間,而不是1個位元組。在Unicode格式的陣列中測試空字元時要當心這一點。我們一般在慣例上說BSTR字串“help”是“一個BSTR型別的字串”。一般公認為,一個BSTR字串變數指向的一個字元陣列中包括至少兩個空字元。就Visual Basic來說,BSTR字串未尾的兩個空位元組可能沒什麼用處,但是對Win32來說,它們卻是至關重要的。原因在於,Win32版本的Unicode String(它稱為LPWSTR)定義為指向一個空字元結尾的Unicode格式的字串的指標。從這個原因上解釋BSTR字串為什麼要以空字元結尾就合情合理了。下面讓我們討論一下C++型別的string變數。還是剛才的那兩句程式碼: Dim str as String str=”help” str表示的是一個BSTR字串變數的名字,而不是一個Unicode格式的字元陣列。換句話說,str是一個儲存地址xxxx(參見圖6-2)的變數的名字。以下是個小小的實驗,它表明Visual Basic中string變數是指向字元陣列的指標而不是字元陣列。下面定義了一個結構,它的成員變數的型別是string。 Private Tyep utTest astring As String bstring As String End Type Dim uTest As utTest Dim s as String s=”testing” uTest.astring=”testing” uTest.astring=”testing” De.Print Len(s) Debug.Print Len(uTest) 這幾句程式碼的結果是: 7 8 對string變數來說,Len函式返回的是字串陣列的字元個數。所以7個字元的字串“testing”返回7。對結構變數uTest來說,Len函式返回的是該結構佔用的記憶體空間。所以返回值8清楚地表明瞭每一個BSTR變數在記憶體中佔用4個位元組。因為BSTR是一個Win32的指標! C型別的LPSTR和LPWSTR 字串 Visual C++使用LPSTR和LPWSTR字串。 LPSTR型別字串的定義是:指向一個空位元組結尾的ANSI格式的字串陣列的指標。但是,因為我們是以空位元組的位置來判斷LPSTR字串的終止的,所以,在LPSTR中是不允許字串中還有第二個空位元組存在。同樣,LPWSTR是一個指向空位元組終止的Unicode格式的字串的指標,它的中間也不允許有空位元組存在。LPWSTR中的W指W,它是微軟對Unicode的另一種說法。LPWSTR如圖6-3所示。 Fig. 6-3.LPSTR and LPWSTR data types 可能我們也會碰到LPCSTR和LPCWSTR型別的字串。其中的C表示Constant(常量)。這種字串是不能被API函式修改的。除此以外,LPCSTR都與LPSTR相同。同理,LPCWSTR除不能修改外,其它的都與LPWSTR相同。再說LPTSTR,LPTSTR一般都用在條件編譯中,就象TCHAR一樣。以下是一個例子程式碼: #ifdef UNICODE typedef LPWSTR LPTSTR; // 在Unicode下LPTSTR與LPWSTR是相同的 typedef LPCWSTR LPCTSTR; // 在Unicode下LPCTSTR與LPCWSTR是相同的 #else typedef LPSTR LPTSTR; //在ANSI下LPTSTR與LPSTR是相同的 typedef LPCSTR LPCTSTR; //在ANSI下LPTCSTR與LPCSTR是相同的 #endif 這幾種型別的圖解如下: Figure 6-4.The LP... STR mess. 大家只須記住,其中的C只不過是只讀的意思就可以了。 有關String這個術語 為了避免任何可能的誤解,以下我們僅使用術語BSTR、Unicode字元陣列和ANSI字元陣列,我們會盡量避開使用string這個術語。如果必須使用String,我們會把它改為Vusual Basic String(就是BSTR)或Visual C++ String(就是以上說過的LP??STR)。然而,在Vusual Basic文件裡,你會經常看到String這個字,至於它是以上所說的三種字串中的哪一種,就要靠你自己的去判斷了。 研究String的工具 如果我們想繼續研究String,那麼我們還需要一些工具。讓我們來看看以下幾個工具。 Visual Basic的StrConv函式: StrConv函式是用來轉變字元陣列的格式的函式。它的語法為: StrConv(string,conversion,LCID)其中的String指的是一個BSTR型別的字串,Conversion是一個常量(接下來馬上就要介紹),而LCID是一個可選的識別符號(在此我們忽略它)。我們感興趣的Conversion引數是以下兩個: VbUnicode(其實叫VbToUnicode更合適) VbFromUnicode 這兩個引數將BSTR字元陣列轉換為Unicode格式或ANS格式I。但是現在我們有個麻煩。我們沒有一個ANSI BSTR型別的字串。定義中已經說過,BSTR字元陣列指向的是Unicode格式的字元陣列。但我們還是可以想象一下,一個ANSI BSTR的字串應該是怎樣的。如同在圖6-2中描述的一樣,只須將Unicode格式的字元陣列用ANSI格式的字元陣列代替就是了。現在我們說StrConv至少有兩種合法的形式: StrConv(an_ANSI_BSTR,vbFromUnicode) StrConv(an_BSTR,vbUnicode)具有諷刺意思的是,在第一種情況的時候,Visual Basic居然不認識自己的函式的返回值!請看下面的程式碼: s = "help" Debug.Print s Debug.Print StrConv(s, vbFromUnicode) 其輸出居然是: help ?? 原因是Visual Basic試圖把ANSI BSTR字串解釋為BSTR字串。請再看下面的程式碼: s = "h" & vbNullChar & "e" & vbNullChar & "l" & vbNullChar & "p" & vbNullChar Debug.Print s Debug.Print StrConv(s, vbFromUnicode) 輸出結果是: h e l p help 在這兒我們為了讓StrConv正常工作,而仿照Unicode格式填充一個字串陣列並傳遞給Visual Basic讓它處理。而這種結果是:一個ANSI BSTR字串於是有了一個合法的BSTR的解釋。這表明,StrConv函式並不真正理解或關心傳過來的字串究竟是BSTR還是ANSI BSTR。它只是設想無論如何你只要傳遞給它一個字串指標就成了,它的任務只是盲目地去轉換這個字元陣列而已。我們以後會發現,很多其它的字串函式都是這樣。這就是說,它們可以接收一個BSTR或是一個ANSI BSTR字元指標,只要這個指標指向的是空位元組結尾的字元陣列就行了。 Len和LenB函式 Visual Basic有兩個返回字串長度的函式:Len和LenB。它們都可以接收一個BSTR或是一個ANSI BSTR,並且返回一個長整型數值。以下的程式碼說明了一切: s = "help" Debug.Print Len(s), LenB(s) Debug.Print Len(StrConv(s, vbFromUnicode)), LenB(StrConv(s, vbFromUnicode)) 輸出結果是: 4 8 2 4 這表明,Len返回的是字元數,而LenB返回的是在BSTR中的位元組數。 Chr,ChrB和ChrW函式這幾個函式的輸入引數和輸出結果都不相同。剛接觸它們時,你可能會感到無所適從。對此,我的建議是:把它們的定義多讀幾遍。 Chr函式接收一個在0到255之間的長整型的整數,返回一個長度為1的BSTR型別字串。此處,BSTR指向的字元是Unicode格式的。所以,雖然長度為1,但實際佔用2位元組。從最新的Visual Basic的文件來看,Chr和Chr$沒有區別。 ChrB函式接收一個在0到255之間的長整型的整數,但它的返回值是一個長度為1位元組的ANSI BSTR型別的字串。該字串是ANSI格式,所以佔用1個位元組。 ChrW函式接收一個在0到255之間的長整型的整數,返回值是一個長度為1的BSTR的字串。該字串是Unicode格式,所以佔用2個位元組。 Asc,AscB和AscW函式這些函式就是Chr的反函式。其中,AscB接收一個ANSI BSTR型別的字串,並返回一個Byte型的該字串第一個字元的ASCII程式碼。為了證明返回值確是Byte型別的資料,請看下面的程式碼: Debug.Print VarType(AscB("h")) = vbByte 輸出結果是True。從字面上來看,可能你會認為AscB會接收一個BSTR型別的字元,但是,實際上它只認這個BSTR字元的第一個位元組,其它的位元組就被忽略掉了。 Asc函式接收一個BSTR型別的字串(不是ANSI BSTR),並返回一個該字串第一個字元的Unicode程式碼。 Null字串和Null字元 Visual Basic允許Null值的BSTR型別字串。請看下面程式碼: Dim s As String s = vbNullString Debug.Print Vtr(s) ‘有關這兩個函式,接下來馬上介紹。 Debug.Print StrPtr(s) 輸出結果是: 1243948 0 這說明,一個Null值的BSTR字串僅僅是一個指向內容為0的字元指標。在Win32和Visual C++中,這種字串叫做空指標。讓我們再來看看vbNullString和vbNullChar的區別。vbNullChar並不是指標,它是一個值為0的Unicode字元。請大家不要把一個值為Null的BSTR和一個空BSTR搞混了。請看下面程式碼: Dim s As String Dim t As String s = vbNullString t = "" 空BSTR字串t是一個指向非空記憶體地址的指標。在那個地址儲存的是空BSTR字串的終止符。而且,它前面的那4個保留位元組中儲存的該字串的長度資訊為0。 VarPtr和StrPtr函式 在微軟的文件裡並沒有VarPtr和StrPtr函式的描述,但它們是非常有用的。特別是VarPtr函式。請看,如果引數var為有效變數的話,那麼, VarPtr(var) 返回的是變數的長整型地址。如果str是一個BSTR字串變數,那麼: StrPtr(str) 返回的是BSTR字串指標指向的Unicode字串的首地址。讓我們看下圖: Figure 6-5. A BSTR 如果程式碼如下: Dim str As String str = "help" 注意,變數str在記憶體中的地址是aaaa,str儲存的內容是字串的首地址xxxx.。請看: VarPtr = aaaa StrPtr = xxxx 請再執行以下程式: Dim lng As Long Dim i As Integer Dim s As String Dim b(1 To 10) As Byte Dim sp As Long, vp As Long s = "help" sp = StrPtr(s ) ’sp是字串首地址即字元h在記憶體中的地址xxxx Debug.Print "StrPtr:" & sp vp = VarPtr(s) ‘vp是sp的儲存的內容xxxx在記憶體中的地址aaaa Debug.Print "VarPtr:" & vp CopyMemory lng, ByVal vp, 4 ‘將長整型指標vp指向的內容xxxx複製到長整型變數lng,並作比較。 Debug.Print lng = sp CopyMemory b(1), ByVal sp, 10 ‘將sp儲存的地址的實際內容複製給byte型陣列b(),並輸出。 For i = 1 To 10 Debug.Print b(i); Next 輸出結果為: StrPtr:1836612 VarPtr:1243988 True 104 0 101 0 108 0 112 0 0 0 在此我們清楚地看到,在記憶體中BSTR型別的字串是以Unicode格式儲存的。請在這幾行程式碼: sp = StrPtr(s) Debug.Print "StrPtr:" & sp 後加入以下幾行程式碼: Dim ct As Long CopyMemory ct, ByVal sp - 4, 4 Debug.Print "Length field: " & ct 執行後可以得到這條輸出結果: Length field: 8 在此我們清楚地看到,這四個位元組的保留字儲存的是字串的位元組數,而不是字元數。 Visual Basic如何傳遞字串到動態連結庫 現在讓我們來面對這個奇怪的問題:Visual Basic如何向外部的動態連結庫傳遞BSTR型別的字串。我們已經知道,Visual Basic內部使用Unicode編碼,也就是說,BSTR字串使用Unicode格式。 NT了使用Unicode編碼,但是,Windows 9x卻不支援Unicode(除非某些特例)。讓我們來看一下一個BSTR變數傳遞到一個外部的動態連結庫(Win32 API或其它)所經過的過程。為了相容Windows 95,Visual Basic總是(甚至在下)建立一個ANSI BSTR字串,將BSTR字串的Unicode格式的字元陣列轉換為ANSI格式,將轉換完成的字元儲存在這個ANSI BSTR字串中。Visual Basic接下來就把這個ANSI BSTR字串傳遞給一個外部的函式。它甚至在需要傳送Unicode字串的Windows NT中也這麼做。 傳送BSTR字串之前的準備 在傳遞一個BSTR字串到一個外部的動態連結庫之前,Visual Basic在其它的記憶體地址中建立一個新的ANSI BSTR字串。然後再把ANSI BSTR字串傳遞給動態連結庫。這種複製並轉移的過程如圖6-6所示。 Figure 6-6. Translating a BSTR to an ABSTR 當我們第一次介紹函式CopyMemory時,我們使用它來演示Unicode到ANSI的轉化過程。現在讓我們用另一種方式來重新演示這個過程。(譯者注:接下來的程式呼叫了《Win32 API Programming with Visual Basic》所附的光碟中的動態連結庫rpiAPI.dll的一個函式rpiBSTRtoByteArray,由於本人沒有該光碟,所以大家無法演示,敬請原諒。在此,我們雖然不能執行該函式,但僅僅拿它來了解作者的程式思想,卻未為不可。)rpiAPI.dll類庫中有一個函式rpiBSTRtoByteArray,它的作用是返回一個傳遞到這個函式的字串的地址值VarPtr和指向值StrPtr。該函式的定義如下: Public Declare Function rpiBSTRtoByteArray Lib "???rpiAPI.dll" ( _ ByRef pBSTR As String, _ ByRef bArray As Byte, _ pVarPtr As Long, _ pStrPtr As Long ) As Long 第一個引數pBSTR為一個BSTR型別的字串,並按引用接收。因此,傳送的是BSTR的地址,而不是字串陣列的首地址。(也就是說,我們傳送了一個字串的二級指標)第二個引數是一個緩衝區,用來存放Visual Basic傳遞過來的ANSI BSTR字串的首字元記憶體地址(注意是按引用傳遞,所以傳遞的應該是地址。)。最後的兩個引數也是緩衝區。函式在pVarPtr變數存放的是BSTR字串指標的地址,pStrPtr存放的是BSTR字串的首地址。函式返回的是字串的字元數。最後,函式會將傳遞給它的字串的首字母改為字元“x”,這樣,透過檢視字串的首字母我們便可以知道動態連結庫接收的是哪一個字串。以下是演示程式碼: Sub BSTRTest() Dim i As Integer Dim sString As String Dim bBuf(1 To 10) As Byte Dim pVarPtr As Long Dim pStrPtr As Long Dim bTarget As Byte Dim lTarget As Long sString = "help" Debug.Print "VarPtr:" & VarPtr(sString) ' 輸出BSTR的地址和指向的字串的地址 Debug.Print "StrPtr:" & StrPtr(sString) Debug.Print "Function called. Return value:" & _ rpiBSTRToByteArray(sString, bBuf(1), pVarPtr, pStrPtr) Debug.Print "Address of temp ABSTR as DLL sees it: " & pVarPtr ‘輸出實際傳遞到外部動態連結庫的ANSI BSTR的地址和字串地址 Debug.Print "Contents of temp ABSTR as DLL sees it: " & pStrPtr Debug.Print "Temp character array: "; ‘輸出臨時建立的傳送到外部動態連結庫的ANSI BSTR字串 For i = 1 To 10 Debug.Print bBuf(i); Next Debug.Print VBGetTarget lTarget, pVarPtr, 4 ‘函式返回後檢查ANSI BSTR字串原先的地址並將其內容輸出 Debug.Print "Contents of temp ABSTR after DLL returns: " & lTarget Debug.Print "BSTR is now: " & sString‘函式返回後輸出原BSTR字串 End Sub 輸出結果為: VarPtr:1242736 StrPtr:2307556 Function called. Return value:4 Address of temp ABSTR as DLL sees it: 1242688 Contents of temp ABSTR as DLL sees it: 1850860 Temp character array: 104 101 108 112 0 0 0 0 0 0 Contents of temp ABSTR after DLL returns: 0 BSTR is now: Xelp 這幾段程式碼首先輸出的是Visual Basic中的BSTR字串指標的地址和字串首字元的地址。接下來呼叫動態連結庫的函式。在動態連結庫返回後,我們試圖輸出ANSI BSTR字串,但得到的結果為0。這說明這種字串是臨時建立的,函式返回後就已經被刪除掉了。最後,大家要知道,我這段程式碼是在Windows NT下執行的。這說明,這種字串轉換在支援Unicode的Windows NT下也發生了!真是莫名其妙! 返回的BSTR字串 對BSTR字串來說,傳送到一個DLL函式並被返回是一個複雜的過程。圖6-7顯示了這整個過程。在ABSTR字串傳遞到DLL函式後,轉換過程就反過來了。原來的BSTR變數str會指向一

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-988218/,如需轉載,請註明出處,否則將追究法律責任。

相關文章