在 VB 中使用 Unicode API (轉)

gugu99發表於2007-12-11
在 VB 中使用 Unicode API (轉)[@more@]

在 中使用 Unicode


介紹

 也許大家都知道, 內部的所有和字串相關的使用的都是 Unicode。

 Unicode 是一套字符集。與其他傳統單一位元組的字符集不同的是,它使用兩個位元組來表示一個字元,這使得可用字元的數量大大增加(理論上說,一個位元組可以包含最多 256 個字元,而兩個位元組可以包含 65536 個字元)。在這裡請注意,不要把 GB2312 之類的雙位元組字符集和 Unicode 混淆:前者既有單位元組字元(如英文)也有雙位元組字元(如中文),這樣使得管理十分麻煩,並且它只支援一種內碼表;而 Unicode 的字元都是雙位元組的,使得管理、轉換和使用字串變得十分容易。並且它支援世界上的所有的常用文字,使得一個可以同時在螢幕上顯示多種語言的文字,而不用關心當前的內碼表。

 好了在簡單地介紹了 Unicode 之後,現在來介紹 Unicode API(什麼?不知道什麼是 API ?(汗)如果是那樣的話這篇文章可能不太適合你)。

  NT 從一開始就在其內部使用 Unicode ,以至於其後續產品(、XP)都一直沿用它。而 Windows 95、98和 ME 就沒有那麼幸運,它們都一直使用單(雙)位元組的字符集。這樣就使得同一個有關字串的 API 有兩個不同字符集的版本:Unicode 版和非 Unicode 版。在函式名上,Unicode 版的 API 具有一個 'W' 字尾代表 w ,如:MessageBoxW;而非 Unicode 版的 API 具有一個 'A' 字尾代表 ANSI ,如:MessageBoxA。
 在 /2000/上這兩種版本的 API 都有,也就是說 Windows NT 支援 Unicode 和非 Unicode 字符集。而在 Windows 95/98/ME 上幾乎所有 API 都只有其非 Unicode 版本,意味著它們之支援單(雙)位元組字符集。


VB 和 Unicode API

 現在言歸正傳。VB 在使用了 Unicode 之後,可以享受到 Unicode 所帶來的各種好處。不過,麻煩也隨之而來了……
 因為 Windows 95/98/ME 不支援 Unicode ,而 VB 只支援 Unicode ,所以當從 VB 裡一個非 Unicode API 時,VB 先要把所有字串都轉換成非 Unicode 字串,然後呼叫 API,最後把所有字串再轉換回 Unicode(見圖1)。這樣使得 API 的呼叫速度變得十分緩慢,而且很低。


┌─────────┐  ┌──────────────┐  ┌────────┐
│         │->│轉換為非Unicode(A)│->│        │
│         │  └──────────────┘  │ API(A) │
│ VB程式(W) │                    │        │
│         │  ┌─────────────┐   └────────┘
│         │<-│轉換為Unicode(W)│<───────┘   
└─────────┘  └─────────────┘          
圖1:呼叫非 Unicode API :需要轉換來轉換去


 看到這裡,也許有的人會問了“那為什麼不用 Unicode API 呢?”在 API 檢視器中的 API 都是非 Unicode 的(Alias 以 'A' 結尾),是因為相容性問題。要讓程式在 Windows 9x 和 NT 裡都能,就必須要呼叫非 Unicode 的 API ,要不然的話會很麻煩;不過這是以為代價的。
 如果程式需要高效能,經常使用需要傳遞字串為引數的 API,而且只執行在 Windows NT/2000/XP 環境下的話(如程式),完全可以用 Unicode API 來替換效率超低的非 Unicode API (見圖2)。


┌─────────┐                    ┌────────┐
│         │───────────────────>│        │
│         │                    │ API(W) │
│ VB程式(W) │                    │        │
│         │                    └────────┘
│         │<-───────────────────────┘   
└─────────┘                           
圖2:呼叫 Unicode API :不需要轉換



實踐

對於剛接觸 API 的程式設計師來說,知道怎樣把現有的非 Unicode API 宣告轉化為 Unicode API 宣告就已經足夠了:


以下是一個簡單的 API 宣告:(作用是獲得一個窗體的標題)

Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
'GetWindowTextA' 指明這是一個非 Unicode API

以下是修改後的 API 宣告:

Declare Function GetWindowText Lib "user32" Alias "GetWindowTextW" (ByVal hwnd As Long, ByVal lpString As Long, ByVal cch As Long) As Long
'GetWindowTextW' 指明這是一個 Unicode API

由此可見,修改一個非 Unicode API 成為一個 Unicode API 的要點是:
 * 把 Alias 裡函式名的結尾 'A' 換成 'W'。
 * 把所有 ByVal *** As String ,改為 ByVal *** As Long 。


以上是宣告方法,以下是呼叫方法:


原來的呼叫方法:  rc = GetWindowText(hwnd, str, strlen)

修改後的呼叫方法: rc = GetWindowText(hwnd, StrPtr(str), strlen)


由此可見,呼叫一個 Unicode API 的要點是把原來應為 字串變數/常量 的地方改為 StrPtr(字串變數/常量) 。



對於接觸 API 有一段時間的程式設計師來說,有必要了解以上宣告方法和呼叫方法的原理。


首先,把 Alias 裡函式名的結尾 'A' 換成 'W' 是因為我們需要呼叫 Windows NT/2000/XP Unicode 版本的 API 。

其次先介紹一下 VB 可變長度字串變數。VB 可變長度字串變數其實是一個指向其 Unicode 字串的指標(見圖3)。


┌───────┐    ┌───────┐
│  ...  │    │  ...  │
│┈┈┈┈┈┈┈│    │┈┈┈┈┈┈┈│
│  變數a  │    │ 字串長度 │
│┈┈┈┈┈┈┈│    │┈┈┈┈┈┈┈│
│  變數b  │  ->│  字元1  │
│┈┈┈┈┈┈┈│ /  │┈┈┈┈┈┈┈│
│ 字串變數 │─   │  字元2  │
│┈┈┈┈┈┈┈│    │┈┈┈┈┈┈┈│
│  變數c  │    │  字元3  │
│┈┈┈┈┈┈┈│    │┈┈┈┈┈┈┈│
│  變數d  │    │  字元4  │
│┈┈┈┈┈┈┈│    │┈┈┈┈┈┈┈│
│  ...  │    │  ...  │
└───────┘    └───────┘
圖3:VB 可變長度字串變數


把所有 ByVal *** As String ,改為 ByVal *** As Long 是因為我們要傳遞 VB Unicode 字串的地址,而不是字串轉換成 ANSI 後的地址。

把原來應為 字串變數/常量 的地方改為 StrPtr(字串變數/常量) 是因為我們可以透過 StrPtr 函式來獲得 Unicode 字串的地址(既圖3中‘字元1’的地址)。


例子

 在知道怎樣轉換和呼叫 Unicode API 之後,讓我們來看一個例子:

' 在 VB 中使用 Unicode API
' 選擇 lstrcpy 這個函式完全是因為所有除了 Windows 95 的 Windows 都支援它的 Unicode 版本,包括 和 ME。
' 使得大家可以在任何 WIndows 95 以上的系統中都可以此例子。(事實上, Windows 95/98/ME 只支援大約 16 個 API 的 Unicode 版本。)

' 此程式只是簡單地用 API 來複制一個字串到另一個字串。該程式只需要一個 Module 就足夠了。請務必把工程的啟動視窗設為 Sub Main() 。

Option Explicit

' lstrcpy API 的作用是複製一個字串到另一個字串。Unicode 和非 Unicode 版本分別為 lstrcpyW 和 lstrcpyA 。
Private Declare Function lstrcpyA Lib "kernel32" (ByVal lpString1 As String, ByVal lpString2 As String) As Long
Private Declare Function lstrcpyW Lib "kernel32" (ByVal lpString1 As Long, ByVal lpString2 As Long) As Long
' 用 timeGetTime API 來計算時間。
Private Declare Function timeGetTime Lib "winmm.dll" () As Long
Private Const COPY_TIMES = 10000& ' 要複製的次數。

Sub Main()
 Dim stra As String, strw As String, strx As String, i As Long
 Dim tm As Long, t1 As Long, t2 As Long

 strx = "" ' 要複製的字串。
 stra = "__________" ' 要用 lstrcpyA 複製到的字串。
 strw = "__________" ' 要用 lstrcpyW 複製到的字串。
 
 tm = timeGetTime
 For i = 1& To COPY_TIMES ' 用 lstrcpyA 複製 COPY_TIMES 次。
 lstrcpyA stra, strx
 Next
 t1 = timeGetTime - tm
 
 tm = timeGetTime
 For i = 1& To COPY_TIMES ' 用 lstrcpyW 複製 COPY_TIMES 次。
 lstrcpyW StrPtr(strw), StrPtr(strx)
 Next
 t2 = timeGetTime - tm
 
 MsgBox "ANSI: 複製 '" & stra & "' " & CStr(COPY_TIMES) & " 次用了 " & CStr(t1) & " 毫秒" & vbCrLf & _
  "Unicode: 複製 '" & strw & "' " & CStr(COPY_TIMES) & " 次用了 " & CStr(t2) & " 毫秒" ' 顯示結果
End Sub

' 以上程式在 Windows 98 Second Edition + Visual Basic 6 Professional Service Pack 5 下除錯透過。
' 結果:非 Unicode API 的執行時間大概是 Unicode API 的 15 倍左右。


結束語

 在 VB 中使用 Unicode API 能夠顯著提高應用程式用 API 處理字串的效率。
 用此方法唯一不足的是相容性問題。不過,已經停止了繼續開發 Windows 95 系列,意味著其以後推出的作業系統都將基於 Windows NT 技術,也意味著它們都將支援 Unicode 。
 換句話說,使用 Unicode API 將成為一項大家都要掌握的技術(其實不應該叫“技術”,因為這很簡單)。


(限於作者水平,此文難免有缺點或錯誤,歡迎批評指正,提出修改建議)

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

相關文章