如何使用 Unicode 版和 Ansi 版 API作者: 西西 (1 篇文章) 日期: 五月 12, 2010 在 4:01 下午1、ANSI字符集和Unicode字符集 ANSI的ASCII字符集及其派生字符集(也稱多字節(jié)字符集)比較舊,Unicode字符集比較新,固定以雙字節(jié)表示一個字。具體見參考鏈接1,也可以看看這篇博文。隨著32位世界和VB4的到來,我們邁進(jìn)了一半是UNICODE,一半是ANSI的Windows世界。而在此之前,是ANSI一統(tǒng)天下。 2、WINDOWS API所用的字符集 操作字符串的API在聲明時,會指定字符集。每個含有字符串的API同時有兩個版本:即ANSI,Unicode。尾部帶A的API是ANSI版本,帶W的API是Unicode版本。例如:SetWindowTextA,是ANSI函數(shù);而SetWindowTextW,是Unicode函數(shù)。 WINUSERAPI BOOL WINAPI SetWindowTextA(HWND hWnd, LPCSTR lpString) 相應(yīng)的,凡涉及到字符集的通知事件都有A和W兩種消息定義。比如,TVN_SELCHANGEDA和TVN_SELCHANGEDW實(shí)際上是兩個不同的通知,其含義是相同的,不同之處在于通知中附帶的結(jié)構(gòu),A結(jié)構(gòu)中使用多字節(jié)字符串,W結(jié)構(gòu)使用Unicode字符串。 3、VB所用的字符集 在VB中,所有字符串按UNICODE保存。如果給A版的API函數(shù)傳字符串參數(shù),這就要求在調(diào)用API函數(shù)之前,將字符串從UNICODE轉(zhuǎn)換成ANSI,函數(shù)執(zhí)行結(jié)束后,將返回的字符串從ANSI轉(zhuǎn)換成UNICODE。如果給W版的函數(shù)傳字符串參數(shù),則不需要做這種轉(zhuǎn)換。如下圖所示: 由于以前大部分API調(diào)用使用的是ANSI字符串(現(xiàn)在已經(jīng)不是這樣了),為了減少用戶調(diào)用API函數(shù)的麻煩,所以VB會自動做這種轉(zhuǎn)換。也就是,只要用“Private/Public Declare ...”語法聲明的函數(shù),VB編譯器認(rèn)為就是API函數(shù),那么只要是用“As String”聲明的參數(shù),VB編譯器一概無條件地把字串由BSTR轉(zhuǎn)換為ANSI傳遞。類似地,任何包含有字符串的結(jié)構(gòu)直接傳給API函數(shù)的話,也會經(jīng)過這種雙重轉(zhuǎn)換。 注意:VB.Net是和VB6完全不同的東東,.Net是Unicode內(nèi)核的,但是有ANSI和Unicode兩種界面。 4、VB6中使用API函數(shù) 上節(jié)提到,VB6 的String傳給API時會自動被轉(zhuǎn)化為ANSI string,從API返回后又被自動轉(zhuǎn)換為unicode String,所以用 VB6/VBA/VBS 最好用ANSI版的API。 雖然這種自動轉(zhuǎn)換對A版函數(shù)是個好事,對W版函數(shù)卻是個麻煩。你想啊,本來人家就想要個Unicode字符串,人程序員給傳的也是Unicode字符串,可是VB6媽媽楞是中間插一手,把人程序員傳過來的Unicode字符串換成ANSI字符串傳了過來,全鬧擰了。所以這種自動轉(zhuǎn)換實(shí)際上使得我們不能將一個字符串類型的參數(shù)以UNICODE方式從VB傳遞給DLL。 如果有些API只有Unicode版本,又需要字符串參數(shù),那怎么辦呢?辦法是:把string類型的參數(shù)轉(zhuǎn)化為指針(以long聲明)傳進(jìn)去。比如,下面兩種聲明: Private Declare Function CharUpperWide Lib "user32" Alias "CharUpperW" (ByVal lpsz As Long) As Long Dim s as String s = "hello" 用第一種聲明,結(jié)果是HELLO;而第二種卻得不到預(yù)期結(jié)果。 因此,我們??梢钥吹?,同樣位置的參數(shù),ANSI的API聲明為string,而UNICODE的聲明為long,像下面這樣: Private Declare Function SetWindowText Lib "user32.dll" Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String) As Long 5、VB6中模擬控件發(fā)送通知消息 兩種字符集的通知都可以在任意字符集的程序中響應(yīng),但前提是控件必須要發(fā)出該類型的通知。通常情況下,控件發(fā)出通知的類型是與程序使用的字符集相同的。比如,VB6里的TreeView控件發(fā)的通知消息應(yīng)該是A型的,與VB一致;但是如果Treeview是CreateWindowExW創(chuàng)建的,就得用W-type 消息。反之,用A-type。 另外,ahao提到,標(biāo)準(zhǔn)控件大部分都有一個消息,比如,TreeView 是 TVM_SETUNICODEFORMAT 、HeaderCtrl 是 HDM_SETUNICODEFORMAT、ListView 是LVM_SETUNICODEFORMAT。作用就是在運(yùn)行期改變控件的字符集,不用重新創(chuàng)建控件,如果設(shè)置為TRUE,就是發(fā)送W版本的Notify消息;如果設(shè)置為FALSE;就是發(fā)送A版本的Notify消息。 6、VarPtr、StrPtr和ObjPtr函數(shù)的用法 上面提到,所有指針都一律定義為Long,但是自己要記得,調(diào)用該API函數(shù)的時候,要通過VarPtr等函數(shù)傳入指針(注意,對應(yīng)的實(shí)體一定不能被釋放掉)。VarPtr/StrPtr/ObjPtr的執(zhí)行速度非常非???,因此調(diào)用UNICODE函數(shù)所造成的系統(tǒng)負(fù)擔(dān)實(shí)際上小于調(diào)用相對應(yīng)的ANSI函數(shù),因?yàn)榍罢卟恍鑆B媽媽對字符串進(jìn)行自動的UA/AU轉(zhuǎn)換。 VarPtr:返回變量地址 為了獲取變量的地址,只須將變量名傳遞給VarPtr函數(shù)就行了。例如: Dim l As Long 類似地,為了獲取字符串的指針,而非保存字符串的變量的指針,只須在變量名前加上ByVal即可。如: Debug.Print VarPtr(s),VarPtr(ByVal s) '例1 不過,現(xiàn)在這種方法貌似又可以用了。以前的 VarPtr 必須通過 Declare 語句聲明,所以有 Unicode-Ansi 自動轉(zhuǎn)換。而到了 VB6,ObjPtr/StrPtr/VarPtr 作為隱藏的內(nèi)部函數(shù)直接提供,就不需要 Unicode-Ansi 自動轉(zhuǎn)換??催@個帖子的0樓和3樓(謝謝Tiger_Zhao)。 該函數(shù)能與要求包含有UNICODE字符串的結(jié)構(gòu)的API調(diào)用一起使用。如果將一個MyUDTVariable變量(一個自定義類型的變量)傳遞給一個由ByRef UDTParam As MyUDT定義的參數(shù),就會發(fā)生ANSI/UNICODE之間的轉(zhuǎn)換。但是,如果將VarPtr(MyUDTVariable)傳遞給由ByVal UDTParam As Long定義的參數(shù),則不會發(fā)生這樣的轉(zhuǎn)換。但是有時你要小心,也許動態(tài)庫期待的一塊連續(xù)的內(nèi)存,這時你傳結(jié)構(gòu)進(jìn)去就未必對了,看這個例子。 6.2 StrPtr出現(xiàn)之前的辦法:字節(jié)數(shù)組 如果有些API只有Unicode版本,你必須自己完成字符串的轉(zhuǎn)換工作才能使用。在VB4中,這必須借助于Byte數(shù)組。 1 將 VB6 的字符串轉(zhuǎn)化成 unicode 內(nèi)碼的字節(jié)數(shù)組 2 把字節(jié)數(shù)組傳給 unnicode 界面的API 3 把API處理結(jié)果又自行轉(zhuǎn)化為 String。 例如: Declare Sub MyUnicodeCall Lib "MyUnicodeDll.dll" (pStr as Byte) Sub MakeCall (MyStr as String) 6.3. StrPtr:返回真正的UNICODE字符串緩沖區(qū)的地址 Declare Sub MyUnicodeCall Lib "MyUnicodeDll.dll" (ByVal pStr as Long) Sub MakeCall (MyStr as String) 也許細(xì)心的朋友會問,為何用Byte數(shù)組的時候要在最后加上vbNullChar,而用StrPtr的時候則不加。因?yàn)镃字符串是以Null結(jié)尾的,如果要給一個DLL函數(shù)傳字符串參數(shù),就要考慮是否要加vbNullChar以防止DLL有可能會越界(越過傳給它的字符串緩沖區(qū)的界)操作內(nèi)存。不過的話,直接傳String參數(shù)作為 char* 調(diào)用、或者用StrPtr()作為 wchar* 調(diào)用,都不需要額外追加vbNullChar。因?yàn)閂B里的字符串都是BSTR結(jié)構(gòu),BSTR 結(jié)構(gòu)包括:長度指示,字符串內(nèi)容,vbNullChar(不計(jì)入長度,如果是Unicode編碼時,占2個字節(jié))。但是如果將字符串賦值給 Byte 數(shù)組,進(jìn)行間接調(diào)用,就需要追加了,因?yàn)橘x值給 Byte 數(shù)組的內(nèi)容不包括 vbNullChar。注:這段解釋來自這個帖子12樓的發(fā)言,感謝Tiger_Zhao。 StrPtr還能用于優(yōu)化ANSI API函數(shù)的調(diào)用。在調(diào)用時使用StrConv和StrPtr就能避免將一個字符串變量多次傳遞給函數(shù)以及為每個調(diào)用而執(zhí)行轉(zhuǎn)換操作所造成的系統(tǒng)負(fù)擔(dān)。例如 '原來的: '現(xiàn)在變?yōu)椋?BR>Declare Sub MyAnsiCall Lib "MyAnsiDll.dll" (ByVal pStr As Long) MyStr=StrConv(MyStr,vbFromUnicode) view plaincopy to clipboardprint? 'str1 未初始化前,也就是真正的 vbNullString,也就是 Null 字符串 'str1初始化為空字符串之后 'str1 未初始化前,也就是真正的 vbNullString,也就是 Null 字符串 'str1初始化為空字符串之后 6.4. ObjPtr 該函數(shù)返回由對象變量引用的接口指針。由于大多數(shù)對象都支持多重接口,因此搞清楚地址對應(yīng)的是對象的哪一個接口就非常重要了。通常這個函數(shù)用于處理放在集合中的對象。通過創(chuàng)建基于對象地址的關(guān)鍵字,你就可以在不需要遍歷整個集合中所有元素的情況下,輕松地將對象從集合中刪除。在許多情況下,對象地址是唯一可靠的能作為關(guān)鍵字的東西,示例如下: ObjCol.Add MyObj1,CStr(ObjPtr(MyObj1)) 6.5. VB6中字符串轉(zhuǎn)換常用函數(shù) 對于VB的字符串,幾個專門“武器”大概有: StrConv() 'unicode與ansi的互換 以Asc、AscB、AscW為例,其區(qū)別如下。 Asc(string) 返回與字符串的第一個字母對應(yīng)的 ANSI 字符代碼。返回值:英文 >0,中文 0,中文 >255 可以下面的例子: view plaincopy to clipboardprint? str1 = "想你So" Debug.Print Asc(str1), AscB(str1), AscW(str1) '-12309 243 24819 Debug.Print Chr(-12309) & "*", ChrB(243) & "*", ChrW(24819) & "*" '想* ? 想* str2 = ChrB(83) & ChrB(0) '"S"由二進(jìn)制83和二進(jìn)制0表示 str1 = "想你So" Debug.Print Asc(str1), AscB(str1), AscW(str1) '-12309 243 24819 Debug.Print Chr(-12309) & "*", ChrB(243) & "*", ChrW(24819) & "*" '想* ? 想* str2 = ChrB(83) & ChrB(0) '"S"由二進(jìn)制83和二進(jìn)制0表示 6.6. 指針啊指針 在6.1節(jié)里我們提到,如果結(jié)構(gòu)里有字符串,那么最好用varptr傳結(jié)構(gòu)的指針過去,以避免無謂的WA轉(zhuǎn)換。不過的話,由于VB里和C里對內(nèi)存的使用不一樣,結(jié)構(gòu)定義得不小心的話,容易導(dǎo)致內(nèi)存溢出。這又是另一個話題了,在另一篇文章里討論吧。 7、小結(jié) (0)在VB6中,用A版API寫代碼簡單(因?yàn)榫幋a轉(zhuǎn)換VB自動給做了),用U版API效率要高些(因?yàn)橛袝r可以省略不必要地編碼轉(zhuǎn)換)。 (1)在VB6中,API調(diào)用中,只要是聲明為string類型的參數(shù)都會自動受到unicode到ansi的轉(zhuǎn)換。 (2)用U版API,對于string型參數(shù),要轉(zhuǎn)化為long,并用StrPrt傳遞字符串緩沖區(qū)指針進(jìn)去。 (3)用U版API,傳結(jié)構(gòu)型參數(shù)時,用varptr傳進(jìn)去可避免不必要的編碼轉(zhuǎn)換,見6.1節(jié);或者也可以用字節(jié)數(shù)組的辦法,見6.2節(jié) (4)用A版API,用strconv和strptr結(jié)合,可提高效率,見6.4節(jié) |
|