1. 程式人生 > >【VBA研究】呼叫API實現漢字簡繁互換

【VBA研究】呼叫API實現漢字簡繁互換

iamlaosong

Excel的VBA功能非常強大,但再強大的東西也需要藉助別的東西,API呼叫就可以完成很多VBA本身沒有的功能,一些已有的功能也是VBA呼叫API實現的。下面的漢字簡繁轉換程式碼,就是呼叫API完成的。

關於API呼叫,網上有很多資料。學習程式設計首先是模仿,我在學習過程中對其中的別名曾經有點困惑,而且看到的資料中別名有的是相同,有的是在名稱後面加一個大寫的字母A,後來通過學習其他資料才明白怎麼回事,原來帶A的名字是該函式在DLL檔案中函式的名字,表示這個函式中字串編碼採用ANSI編碼。

'中文簡體與繁體的互換
Private Declare Function LCMapString Lib "kernel32" Alias "LCMapStringA" (ByVal Locale As Long, _
ByVal dwMapFlags As Long, ByVal lpSrcStr As String, ByVal cchSrc As Long, ByVal lpDestStr As String, ByVal cchDest As Long) As Long
Private Declare Function lStrLen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As String) As Long
'轉換函式,0=簡到繁,其他=繁到簡
Function Jian_Fan_Conv(ByVal strString As String, Optional ByVal iMode As Integer = 0) As String
    Dim lStrLength As Long
    Dim strNew As String
    Const J2F_MAPFLAG = &H4000000
    Const F2J_MAPFLAG = &H2000000
    Jian_Fan_Conv = ""
    lStrLength = lStrLen(strString)
    strNew = Space(lStrLength)
    If iMode = 0 Then
        LCMapString &H804, J2F_MAPFLAG, strString, lStrLength, strNew, lStrLength
    Else
        LCMapString &H804, F2J_MAPFLAG, strString, lStrLength, strNew, lStrLength
    End If
    Jian_Fan_Conv = strNew
End Function

Sub j2f()
    sj = "駑馬十駕,才定不捨。"
    sf = Jian_Fan_Conv(sj)
    MsgBox sj & sf
    sx = Jian_Fan_Conv(sj, 1)
    sy = Jian_Fan_Conv(sf, 1)
    MsgBox sx & sy

End Sub

執行結果截圖:

Declare語句用法如下:

下面的示例是 GetTempPath 函式的 Declare 語句,該函式返回 Microsoft® Windows® 臨時資料夾的路徑:

Private Declare Function GetTempPath Lib "kernel32" _
Alias "GetTempPathA" (ByVal nBufferLength As Long, _
ByVal lpBuffer As String) As Long
關鍵字 Declare 告訴 VBA 您想在自己的專案中加入一個 DLL 函式定義。標準模組中的 Declare 語句可以是公共或私有的,這取決於您是想只讓某個模組使用該 API 函式還是整個專案均可用。在類模組中,Declare 語句必須是私有的。

關鍵字 Function 後面的函式名是用來在 VBA 中呼叫該函式時用的名稱。這個名稱可以與 API 函式本身的名稱相同,或者在 Declare 語句中使用關鍵字 Alias,表示您打算在 VBA 中用另外的名稱(別名)來呼叫該函式。

在前面的例子中,DLL 中 API 函式的名稱是 GetTempPathA,而從 VBA 中呼叫它的名稱是 GetTempPath。

注意:關鍵字 Alias 後面的是 DLL 函式的真實名稱。
注意:GetTempPath 是 Win32API.txt 檔案給該函式起的別名,您可把它更改為任何想要的名稱。

在 Declare 語句中使用別名的原因有以下幾點:

1、一些API函式的名稱以下劃線 (_) 開頭,這在 VBA 中是非法的。在 VBA 中必須使用別名來呼叫某函式。
因為別名允許程式設計師隨意給 DLL 函式命名,使函式名符合您在 VBA 中的命名標準。
因為 API 函式區分大小寫而 VBA 函式不區分,所以可使用別名來更改函式名的大小寫。
2、一些DLL函式帶有能接受不同資料型別的引數。這些函式的 VBA Declare 語句把這些引數的型別定義為 Any。呼叫聲明瞭 Any 型別引數的 DLL 函式有一定的風險,因為 VBA 不做任何資料型別檢查。如果想避免這種傳遞 Any 型別引數帶來的危險,可以宣告同一個 DLL 函式的多個版本,每個都有不同的名稱和資料型別。
在 Windows API 中,所有帶有字串引數的函式都有兩個版本:一個 ANSI 版本和一個 Unicode 版本。ANSI 版的函式名帶有後綴 "A"(見前面的例子),而 Unicode 版的字尾是 "W"。雖然 VBA 內部使用 Unicode 版本,但它在呼叫 DLL 函式前會把所有的字串轉為 ANSI 字串,所以,在 VBA 中呼叫 Windows API 函式時一般都用 ANSI 版。API 檢視器外接程式自動給所有帶有字串引數的函式取別名,所以在呼叫函式時不必包括字尾 "A"。
關鍵字 Lib 指明要呼叫的函式在哪個 DLL 中。請注意,在 Declare 語句裡,DLL 的名稱被包含在一個字串中。如果使用者的系統中找不到關鍵字 Lib 後面指定的 DLL,對函式的呼叫將失敗,併產生一個代號為 48 的執行時錯誤,“呼叫 DLL錯誤”。由於這種錯誤可在 VBA 程式碼中處理,所以可以編寫程式碼來體面地應付錯誤。

注意:如果呼叫的函式位於某一基本的 Windows DLL 中就不存在這種問題,因為要載入應用程式,這些 DLL 肯定存在。

下面給出了 Windows API 中最常用的的 DLL。

DLL 包括Kernel32.dll 低層作業系統函式,例如管理記憶體和資源的函式。
User32.dll Windows 管理函式,例如負責訊息處理、計時器、選單和通訊的函式。
GDI32.dll 圖形裝置介面 (GDI) 庫,包括負責裝置輸出的函式,例如負責繪圖、顯示上下文和字型管理的函式。

大部分 DLL 都是用 C 或 C++ 語言編寫的,包括那些 Windows API 中的 DLL。因為 C 或 C++ 函式對引數和資料型別的要求在幾個方面與 VBA 函式不同,所以,在給 DLL 函式傳遞引數時,需要對 C 或 C++ 函式要求的引數和資料型別有一定了解。

而且,DLL 函式的許多引數是以傳值方式傳遞的。預設情況下,VBA 中的引數以傳址方式傳遞,所以當 DLL 函式要求引數是以傳值方式傳遞時,必須在函式定義裡包含關鍵字 ByVal。如果函式定義中漏掉關鍵字 ByVal,可能會在應用程式中引起非法頁面錯誤。或者可能會出現代號為 49 的 VBA 執行時錯誤,“錯誤的 DLL 呼叫約定”。

以傳址方式傳遞引數就是把該引數在記憶體中的位置傳遞給被呼叫的過程。如果該過程修改了引數的值,實際上改變的是該引數唯一的副本,所以當執行返回到呼叫過程時,引數中包含的是已修改的值。

而以傳值方式向 DLL 函式傳遞引數實際上是傳遞了該引數的一個副本,函式只對這一副本進行操作。這可以防止函式修改實際引數的內容。當執行返回到呼叫過程時,引數包含的值與呼叫其他過程前相同。

因為以傳址方式傳遞允許在記憶體中修改引數,所以如果以傳址方式傳遞了錯誤引數,DLL 函式可能會錯誤地改寫記憶體,從而造成錯誤或其他意想不到的行為。Windows 有許多值不應該改寫。例如 Windows 為每個視窗分配了一個唯一的 32 位識別符號,稱為控制代碼。控制代碼是以傳值方式傳遞給 API 函式的,因為如果 Windows 修改了某視窗的控制代碼,就不能再跟蹤該視窗了。

注意:雖然關鍵字 ByVal 出現在一些 String 型別的引數前,但字串總是以傳址的方式傳遞給 Windows API 函式。

要從DLL中傳值回來,字元型必須賦值站位(分配記憶體空間),數值型引數必須用傳址方式(ByRef)。

Private Declare Function GetWindowsDirectory Lib "kernel32" Alias _
"GetWindowsDirectoryA" (ByVal lpBuffer As String, ByVal nSize As Long) As Long

Private Sub Command1_Click()
    Dim sBuffer As String
    sBuffer = Space(255)
    GetWindowsDirectory sBuffer, 255
    MsgBox "Windows目錄在:" & sBuffer
EndSub