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

分享

如何使用 Unicode 版和 Ansi 版 API – 中文

如何使用 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)
WINUSERAPI BOOL WINAPI SetWindowTextW(HWND hWnd, LPCWSTR 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
Private Declare Function CharUpperWide Lib "user32" Alias "CharUpperW" (ByVal s As String) As Long
用下面的代碼調(diào)用

Dim s as String s = "hello"
Call CharUpperWide(StrPtr(s))
Debug.Print s

用第一種聲明,結(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
Private Declare Function SetWindowText Lib "user32.dll" Alias "SetWindowTextW" (ByVal hwnd As Long, ByVal lpString As Long) 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:返回變量地址
StrPtr:返回真正的UNICODE字符串緩沖區(qū)的地址
ObjPtr:返回任何對象變量引用的地址
6.1 VarPtr

為了獲取變量的地址,只須將變量名傳遞給VarPtr函數(shù)就行了。例如:

Dim l As Long
Debug.Print VarPtr(l)

類似地,為了獲取字符串的指針,而非保存字符串的變量的指針,只須在變量名前加上ByVal即可。如:

Debug.Print VarPtr(s),VarPtr(ByVal s) '例1
在VB3之前,用這種方法來獲取字符串緩沖的指針是非常普遍的。但是由于VB6的UNICODE和ANSI字符串的自動變化機(jī)制,和當(dāng)一個字符串傳遞給VarPtr函數(shù)時,函數(shù)執(zhí)行后所返回的地址是保存臨時ANSI字符串的臨時ANSI字符串或變量的地址。換句話說,這個地址并不是你聲明的變量的真正地址。因此,對于字符串變量以及包括字符串的結(jié)構(gòu)來講,例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)
Dim bTmp() as Byte
bTmp=MyStr & vbNullChar '-- 這里為何要加個vbNullChar?
MyUnicodeCall bTmp(0)
MyStr=bTmp
MyStr=left(MyStr, Len(MyStr)-1)
End Sub

6.3. StrPtr:返回真正的UNICODE字符串緩沖區(qū)的地址
如果使用StrPtr,上面的代碼精簡為:

Declare Sub MyUnicodeCall Lib "MyUnicodeDll.dll" (ByVal pStr as Long)

Sub MakeCall (MyStr as String)
MyUnicodeCall StrPtr(MyStr)
End Sub

也許細(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)。例如

'原來的:
Declare Sub MyAnsiCall Lib "MyAnsiDll.dll" (ByVal pStr As String)
MyAnsiCall MyStr

'現(xiàn)在變?yōu)椋?BR>Declare Sub MyAnsiCall Lib "MyAnsiDll.dll" (ByVal pStr As Long)

MyStr=StrConv(MyStr,vbFromUnicode)
MyAnsiCall StrPtr(MyStr)
MyStr=StrConv(MyStr,vbUnicode) '注釋:并不總是要求
StrPtr還是唯一能直觀地告訴你空字符串和null字符串的不同的方法。對于null字符串(vbNullString),StrPtr的返回值為0,而對于空字符串,函數(shù)的返回值為非零??聪旅娴睦樱ㄝ敵鼋Y(jié)果在注釋內(nèi)):

view plaincopy to clipboardprint?
Sub Test1_Null()
Dim str1 As String, str2 As String

'str1 未初始化前,也就是真正的 vbNullString,也就是 Null 字符串
str2 = vbNullChar
Debug.Print str1 = str2, Len(str2), Len(str1) 'False, 1, 0
Debug.Print str1 = vbNullString, StrPtr(str1), Len(str1) 'True, 0, 0

'str1初始化為空字符串之后
str1 = ""
Debug.Print str1 = vbNullString, StrPtr(str1), Len(str1) 'True, 165209884, 0
Debug.Print str1 = str2 'False
End Sub
Sub Test1_Null()
Dim str1 As String, str2 As String

'str1 未初始化前,也就是真正的 vbNullString,也就是 Null 字符串
str2 = vbNullChar
Debug.Print str1 = str2, Len(str2), Len(str1) 'False, 1, 0
Debug.Print str1 = vbNullString, StrPtr(str1), Len(str1) 'True, 0, 0

'str1初始化為空字符串之后
str1 = ""
Debug.Print str1 = vbNullString, StrPtr(str1), Len(str1) 'True, 165209884, 0
Debug.Print str1 = str2 'False
End Sub

6.4. ObjPtr

該函數(shù)返回由對象變量引用的接口指針。由于大多數(shù)對象都支持多重接口,因此搞清楚地址對應(yīng)的是對象的哪一個接口就非常重要了。通常這個函數(shù)用于處理放在集合中的對象。通過創(chuàng)建基于對象地址的關(guān)鍵字,你就可以在不需要遍歷整個集合中所有元素的情況下,輕松地將對象從集合中刪除。在許多情況下,對象地址是唯一可靠的能作為關(guān)鍵字的東西,示例如下:

ObjCol.Add MyObj1,CStr(ObjPtr(MyObj1))
.....
ObjCol.Remove CStr(ObjPtr(MyObj1))

6.5. VB6中字符串轉(zhuǎn)換常用函數(shù)

對于VB的字符串,幾個專門“武器”大概有:

StrConv() 'unicode與ansi的互換
VarPtr() '-- 獲得字符串變量的地址
StrPtr() '-- 獲得字符串緩沖區(qū)的地址
Asc(), AscB(), AscW()
Chr(), ChrB(), ChrW()
Len(), LenB()
vbNullString, vbNullChar

以Asc、AscB、AscW為例,其區(qū)別如下。

Asc(string) 返回與字符串的第一個字母對應(yīng)的 ANSI 字符代碼。返回值:英文 >0,中文 0,中文 >255

可以下面的例子:

view plaincopy to clipboardprint?
Sub Test2_StrFunc()
Dim str1 As String, str2 As String

str1 = "想你So"
str2 = "So想你"

Debug.Print Asc(str1), AscB(str1), AscW(str1) '-12309 243 24819
Debug.Print Asc(str2), AscB(str2), AscW(str2) ' 83 83 83

Debug.Print Chr(-12309) & "*", ChrB(243) & "*", ChrW(24819) & "*" '想* ? 想*
Debug.Print Chr(83) & "*", ChrB(83) & "*", ChrW(83) & "*" 'S* ? S*

str2 = ChrB(83) & ChrB(0) '"S"由二進(jìn)制83和二進(jìn)制0表示
Debug.Print str2 'S
End Sub
Sub Test2_StrFunc()
Dim str1 As String, str2 As String

str1 = "想你So"
str2 = "So想你"

Debug.Print Asc(str1), AscB(str1), AscW(str1) '-12309 243 24819
Debug.Print Asc(str2), AscB(str2), AscW(str2) ' 83 83 83

Debug.Print Chr(-12309) & "*", ChrB(243) & "*", ChrW(24819) & "*" '想* ? 想*
Debug.Print Chr(83) & "*", ChrB(83) & "*", ChrW(83) & "*" 'S* ? S*

str2 = ChrB(83) & ChrB(0) '"S"由二進(jìn)制83和二進(jìn)制0表示
Debug.Print str2 'S
End Sub

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é)

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

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多