在VBA中使用Windows API


VBA是一種強大的編程語言,可用於自定義Microsoft Office解決方案。通過使用VBA處理一個或多個Office應用程序對象模型,可以容易地修改Office應用程序的功能或者能夠使兩個或多個Office應用程序協同工作以完成單個應用程序無法完成的任務。然而,使用VBA僅能控制操作系統的一小部分。Windows API提供了控制操作系統絕大多數方面的功能。下面,介紹在VBA中使用Windows API的一些知識。

理解APIs

API只是一組函數,可用於處理組件、應用程序或操作系統。通常,API由一個或多個提供某種特定功能的DLLs組成。

DLLs是包含函數的文件,能夠從任何運行的Windows應用程序中調用DLLs。在運行時,DLL中的函數被動態鏈接到調用它的應用程序里。無論多少應用程序調用DLL中的函數,該函數僅存在於磁盤的單個文件中,並且DLL在內存中僅被創建一次。

您可能最經常聽說的API是Windows API,它包括組成Windows操作系統的DLLs。每個Windows應用程序都直接或間接地與Windows API相交互,Windows API確保運行在Windows下的所有應用程序都按一致的方式工作。

除了Windows API外,還有其它發布的APIs可用。例如,郵件應用程序編程接口(MAPI)是一組用於編寫電子郵件應用程序的DLLs。

APIs通常是由創建Windows應用程序的C和C++程序員編寫,但能夠使用VBA調用DLL中的函數。因為大多數DLLs最初都是由C/C++程序員編寫和文檔規范,所以調用DLL函數與調用VBA函數不同。為了使用API,必需理解如何傳遞參數到DLL函數。

為了調用Windows API中的函數,需要描述這些可用的函數的文檔規范,如何在VBA中聲明這些函數,以及如何調用它們。下面是兩個有用的資源:

1、Win32API.txt文件,包含Windows API中大多數函數的VBA Declare(聲明)語句。可以使用API Viewer加載宏查找和復制需要的Declare語句。可以在下面的站點下載API聲明查看器:

http://www.activevb.de/rubriken/apiviewer/index-apiviewereng.html

win32api.txt文件下載:

2、Microsoft Platform SDK,包含復雜的Windows API文檔。可以在下面的地址中查看:http://msdn.microsoft.com/en-us/library/aa383750(VS.85).aspx

此外,很多程序員還開發了一些聲明並與大家共享,下面就是一個關於API聲明的資源網站:http://www.xcelfiles.com/

使用Declare語句

在從VBA中調用DLL里的函數之前,必須為VBA提供在哪里找到函數以及如何調用該函數的信息,有兩種方法:

1、設置對DLL類型庫的引用。

2、在模塊中使用Declare語句。

設置對DLL類型庫的引用是使用DLL中的函數的最容易的方法。一旦設置引用,就可以將其當作工程里的一部分一樣調用DLL函數。然而,也要注意一些事項。首先,設置對多個類型庫的引用會影響應用程序的性能;其次,不是所有的DLLs都提供類型庫,雖然可以對沒有提供類型庫的DLL設置引用,但不能調用該DLL中的函數。

注意,組成Windows API的DLLs沒有提供類型庫,因此不能設置對它們的引用並調用其中的函數。要調用Windows API中的函數,必須在工程里模塊的聲明部分包括Declare語句。

Declare語句是一個定義,告訴VBA在哪里找到特定的DLL函數以及如何調用該函數。在代碼中添加Declare語句最簡單的辦法是使用API Viewer加載宏,其中包含Windows API中大多數函數的Declare語句,也包含一些函數所需要的常量和類型定義。

Declare語句聲明的形式如下:

[Public|Private]Declare Sub name Lib "libname" [Alias "aliasname"][([arglist])]
[Public|Private]Declare Function name Lib "libname" [Alias "aliasname"] [([arglist])] [As type]

下面是GetTempPath函數的Declare語句的示例,該函數返回Windows臨時文件夾的路徑(默認為C:\Windows\Temp):

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。注意,DLL函數的實際名字出現在關鍵字Alias之后,同時也注意到GetTempPath是Win32API.txt文件用於該函數的別名,但你可以將其改變為任何你想要的名字。

下面是為什么要在Declare語句中使用別名的一些理由:

  • 一些API函數的名字以下划線(_)開始,在VBA中是不合乎語法的。為了從VBA中調用該函數,需要使用別名。
  • 因為別名允許將DLL函數命名為你所希望的名字,所以可以使函數名字遵循你自已在VBA中的命名標准。
  • 因為API函數是區分大小寫的,而VBA函數則不,所以可以使用別名來改變函數名的大小寫。
  • 一些DLL函數帶有接受不同數據類型的參數,這些函數的VBA聲明語句定義這些參數為類型Any,調用帶有聲明為Any的參數的DLL函數是危險的,因為VBA不會執行任何數據類型檢查。如果想避免傳遞類型為Any的參數的危險,可以聲明相同的DLL函數的多個版本,每一個都具有不同的名字和不同的數據類型。
  • Windows API為所有接受字符串參數的函數都包含兩個版本:ANSI版和Unicode版。ANSI版帶有"A"后綴,正如上例所示,而Unicode版帶有"W"后綴。雖然VBA使用Unicode,但在調用DLL中的函數之前,它將所有的字符串轉換為ANSI字符串,因此在從VBA中調用Windows API函數時通常使用ANSI版。API Viewer加載宏自動為所有接受字符串參數的函數命名別名,因此可以不必包含"A"后綴而調用該函數。

關鍵字Lib指定包含函數的DLL。注意,在聲明語句里以字符串形式包含DLL的名字。如果在系統中沒有找到關鍵字Lib之后指定的DLL,對該函數的調用將失敗,導致運行時錯誤:48,裝載DLL錯誤。因為可以在VBA代碼中處理這種錯誤,所以可以編寫健壯的代碼得體地處理錯誤。

下面列出了Windows API中最常使用的DLLs:

  • Kernel32.dll:低級別的操作系統函數,例如內存管理和資源處理。
  • User32.dll:Windows管理函數,例如消息處理、計時器、菜單和通訊。
  • GDI32.dll:圖像設備接口(GDI)庫,包含設置輸出的函數,例如繪圖、顯示上下文和字體管理。

大多數DLLs,包括Windows API中的DLLs,都采用C/C++編寫,因此,傳遞參數到DLL函數需要參數的理解以及C/C++接受的數據類型,而這些不同於VBA函數。

同時,DLL函數的許多參數按值傳遞。默認情況下,VBA中的參數按引用傳遞。因此,當DLL函數需要按值傳遞的參數時,在函數定義中包括關鍵字ByVal是必要的。在函數定義中忽略ByVal關鍵字可能會在應用程序中導致無效的頁錯誤。有時,可能會發生VBA運行時錯誤:49,壞的DLL調用協議。

按引用傳遞參數傳遞該參數的內存位置到被調用的過程,如果該過程修改了參數的值,那么會修改該參數的唯一的副本,因此,當返回到調用過程時,參數包含的是修改后的值。

按值傳遞參數到DLL函數,將傳遞該參數的副本,函數操作該參數的副本,避免了修改實際參數的內容。當返回到調用過程時,該參數包含與調用其它過程前相同的值。

因為按引用傳遞允許在內存中修改參數值,如果不恰當地按引用傳遞參數,DLL函數可能會覆蓋它不應該覆蓋的內存,導致錯誤或者不可預料的結果。Windows維護許多值不應該被覆蓋,例如,Windows為每個窗口賦惟一的32位標識符,稱作句柄(handle)。句柄總是按值傳遞給API函數,因為如果Windows修改了某窗口的句柄,那么不再能夠追蹤到該窗口。(雖然關鍵字ByVal出現在String類型的一些參數前面,但是字符串總是按引用被傳遞到Windows API函數)

上述聲明語句接受兩個參數,一個為Long型,另一個為String型,並返回一個Long型值。

使用常量

除了DLL函數的聲明語句外,一些函數還需要定義常量以及在函數中使用的類型。在模塊的聲明部分包括常量和用戶定義類型。

如何知道函數需要的常量和用戶定義類型呢?需要查看該函數的文檔。Win32API.txt文件包含函數的常量和用戶定義類型的定義。可以使用API Viewer加載宏找出這些常量和用戶定義類型,並將它們復制到代碼中。不巧的是,常量和用戶定義類型不會以任何方式與需要它們的聲明語句相聯系,因此,仍然需要檢查DLL函數的文檔,決定哪個常量和類型與哪個聲明語句匹配。

函數可能需要傳遞常量來指明想要函數返回的信息。例如,GetSystemMetrics函數接受75個常量,每一個都指定操作系統的不同方面,該函數返回的信息取決於傳遞給它的常量。要調用GetSystemMetrics,不需要包括所有的75個常量,只需包括要使用的就可以了。

建議定義常量而不是簡單地傳遞它們代表的值。Microsoft確保在將來的版本中仍然會保留相同的常量,但不保證常量的值相同。

DLL函數需要的常量通常是隱含的,因此需要查閱函數的文檔來確定傳遞的常量,以返回特定的值。

在《Professional Excel Development》中介紹了如何查找常量的值的方法。即在Microsoft的站點下載並安裝核心SDK軟件包,其中有一個名為"include"的子目錄,所有用於創建動態鏈接庫(DLL)的C++頭文件都存放在這個目錄中。通過搜索就能找到常量所在的文件,例如查找SM_CXSCREEN,會返回文件"winuser.h",打開該文件查詢就可找到相關的常量。

下面的示例是包括GetSystemMetrics函數的聲明語句,接受兩個常量,然后展示如何從屬性過程中調用GetSystemMetrics,以像素為單位返回屏幕的高度。

Declare Function GetSystemMetrics Lib "User32" (ByVal nIndex As Long) As Long
Const SM_CXSCREEN As Long = 0 '屏幕寬度
Const SM_CYSCREEN As Long = 1 '屏幕高度

Public Property Get ScreenHeight() As Long
'以像素為單位返回屏幕的高度
ScreenHeight = GetSystemMetrics(SM_CYSCREEN)

End Property
 
Public Property Get ScreenWidth() As Long
'以像素為單位返回屏幕的寬度
ScreenWidth = GetSystemMetrics(SM_CXSCREEN)

End Property

使用用戶定義類型

用戶定義類型是一種數據結構,可以存儲多個相關的不同類型的變量,與C/C++中的結構一致。有時,傳遞空的用戶定義類型到DLL函數,函數填充值;有時,從VBA填充用戶定義類型,並將其傳遞給DLL函數。

可以將用戶定義類型作為一箱抽屜,每個抽屜可以包含不同類型的項目,但將它們組合在一起可以當作相關項目的單個箱子。可以從任何抽屜獲得項目而不必擔心存儲在任何其它抽屜中的項目。

要創建用戶定義類型,使用Type … End Type語句。在Type…End Type語句里,列出了每個項目,包含值和數據類型。用戶定義類型的元素可以是數組。

下面的代碼段展示如何定義RECT用戶定義類型,和管理屏幕矩形塊的幾個Windows API函數一起使用。例如,GetWindowRect函數接受RECT類型的數據結構,使用關於窗口的左側、頂部、右側和底部位置的信息填充。

Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type

要傳遞用戶定義類型到DLL函數,必須創建該類型的變量。例如,如果打算傳遞RECT類型的用戶定義類型到DLL函數,那么就要包括變量聲明,如下所示:

Private rectWindow As RECT

可以引用用戶定義類型里的單個元素,如下所示:

Debug.Print rectWindow.Left

使用句柄

調用DLLs中的函數之前需要理解的另一個重要的概念是句柄(handle)。簡單地說,句柄是32位正整數,Windows用於識別窗口或另一個對象,例如字體或位圖。

在Windows中,窗口有許多不同的表現形式。事實上,在屏幕中看到的幾乎所有事情都在窗口里,並且不能看到的大多數事情也在窗口里。窗口能夠是一個綁定的屏幕矩形區域,就像您習慣看到的應用程序窗口一樣。窗體中的控件,例如列表框或滾動條,也都是窗口,雖然不是所有類型的控件都是窗口。在桌面上顯示的圖標以及桌面本身,都是窗口。

因為所有這些類型的對象都是窗口,所以Windows能夠相同地對待它們。Windows提供給每個窗口一個唯一的句柄,並使用該句柄去處理窗口。許多API函數返回句柄或者接受句柄作為其參數。

當窗口創建時Windows賦句柄給該窗口,當窗口銷毀時Windows釋放該句柄。雖然句柄保留的時間與窗口存在的時間相同,但不保證一個窗口在銷毀並重新創建后有相同的句柄。因此,如果在變量中存儲句柄,那么記住該窗口銷毀后,該句柄不再有效。

GetActiveWindow函數是返回窗口句柄的函數示例,此時,應用程序窗口是當前活動的窗口。GetWindowText函數接受某窗口的句柄,並且如果窗口有標題的話返回該窗口的標題。下面的程序使用GetActiveWindow返回活動窗口的句柄,GetWindowText返回其標題:

Declare Function GetActiveWindow Lib "user32" () As Long
Declare Function GetWindowText Lib "user32" _

Alias "GetWindowTextA" (ByVal Hwnd As Long, _
ByVal lpString As String, ByVal cch As Long) As Long
 
Function ActiveWindowCaption() As String
Dim strCaption As String
Dim lngLen As Long
'創建使用空字符填充的字符串
strCaption = String$(255, vbNullChar)
'返回字符串的長度
lngLen = Len(strCaption)
'調用GetActiveWindow來返回活動窗口的句柄
'與字符串和其長度一起,傳遞句柄到GetWindowText
If (GetWindowText(GetActiveWindow, strCaption, lngLen) > 0) Then
'返回Windows已寫入的值給字符串
ActiveWindowCaption = strCaption
End If
End Function

GetWindowText函數接受三個參數:窗口的句柄、將返回窗口標題里的空結尾的字符串、以及字符串的長度。

下面列出了Excel中常用的窗口類名稱:

  • Excel主窗口:XLMAIN
  • Excel桌面:XLDESK
  • Excel工作表:EXCEL7
  • Excel用戶窗體:ThunderDFrame(Excel 2000以后版本)、ThunderRT6DFrame(Excel 2000以后版本,用於作為COM加載項時)、ThunderXFrame(Excel 97)
  • Excel狀態欄:EXCEL4
  • Excel圖表窗口:EXCELE(Excel2007以前版本)

FindWindow函數使用類名和窗口標題查找窗口。下面的代碼以像素為單位查找Excel主窗口的位置和大小:

'包含窗口大小的用戶定義類型
Type
RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
 

'查找窗口的API函數
Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" ( _
ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
 
'獲取窗口大小的API函數
Declare Function GetWindowRect Lib "user32" ( _
ByVal hWnd As Long, _
lpRect As RECT) As Long
 
Sub ShowExcelWindowSize()
Dim hWnd As Long, uRect As RECT
'獲取Excel主窗口的句柄
'Excel 2002及以后版本也可使用hWnd=Application.Hwnd
hWnd = FindWindow("XLMAIN", Application.Caption)
'將窗口大小信息存入到RECT結構中
GetWindowRect hWnd, uRect
'顯示結果
MsgBox "這個Excel窗口的尺寸為:" & _
vbCrLf & "左側:" & uRect.Left & _
vbCrLf & "右側:" & uRect.Right & _
vbCrLf & "頂部:" & uRect.Top & _
vbCrLf & "底部:" & uRect.Bottom & _
vbCrLf & "寬度:" & (uRect.Right - uRect.Left) & _
vbCrLf & "高度:" & (uRect.Bottom - uRect.Top)
End Sub

調用函數

雖然調用DLL函數的許多方式與調用VBA函數相似,但是開始時有一些不同可能會使DLL函數混淆。下面將介紹如何輸入DLL函數中的參數並加前綴、如何返回字符串、如何傳遞數據結構、能夠接受什么返回值、以及如何獲取錯誤信息。

參數數據類型

在C/C++中使用的數據類型、用於描述它們的標記都不同於在VBA中的用法,下面描述了DLL函數中常用的數據類型以及它們在VBA中的等效表示。

   

   

C/C++數據類型

匈牙利前綴

描述

等效的VBA表示

BOOL

b

8位布爾值。0表示False;非0表示True

Boolean或Long

BYTE

ch

8位無符號整數

Byte

HANDLE

h

32位無符號整數,代表Windows對象的句柄

Long

int

n

16位符號整數

Integer

long

l

32位符號整數

Long

LP

lp

32位對內存中C/C++結構、字符串、函數或其它數據的長指針

Long

LPZSTR

lpsz

32位對C類型空結尾字符串的長指針

Long

雖然您應該熟悉這些數據類型和前綴,但前面提到的Win32API.txt文件包含了准備在VBA中使用的聲明語句。如果在代碼中使用這些聲明語句,那么函數參數已經定義了正確的VBA數據類型。

在《Excel 2007 VBA參考大全》的第27章,詳細介紹了如何將C-樣式聲明轉換為VBA聲明語句。

只要已經定義並傳遞了正確的數據類型,調用DLL函數與調用VBA函數采取相同的方法。當然也有例外,這將在下面的內容中介紹。

從DLL函數中返回字符串

DLL函數不會以VBA函數相同的方法返回字符串。因為字符串總是按引用傳遞到DLL函數,DLL函數能夠修改字符串參數的值。寧可返回字符串作為函數的返回值,就像可能在VBA中做的那樣,DLL函數返回字符串到傳遞給該函數的String類型的參數。函數的實際返回值經常是一個長整型值,指定寫入到字符串參數的字節數量。

接受字符串參數的DLL函數獲得指針,指向內存中該字符串的位置。指針只是內存地址,表明在哪里存儲字符串。因此,當從VBA中傳遞字符串到DLL函數時,傳遞給DLL函數一個指針,指向內存中的字符串。接着,這個DLL函數修改存儲在那個地址的字符串。

要調用寫到String變量的DLL函數,需要采取額外的步驟合適地格式字符串。首先,String變量必須是空結尾字符串。一個空結尾字符串以特定的空字符結束,空字符通過VBA常量vbNullChar來指定。

其次,DLL函數不能修改已經創建的字符串的大小。因此,需要確保傳遞給函數的字符串足夠大以容納整個返回值。當傳遞字符串到DLL函數中時,通常需要指定在另一個傳遞的參數中字符串的大小。Windows追蹤字符串的長度,以確保不會覆蓋掉字符串已使用過的內存。

傳遞字符串到DLL函數中的一個好方法是創建String變量,並使用String$函數在其中填充空字符,使其足夠大以容納函數返回的字符串。例如,下面的代碼創建一個144字節長的字符串,並使用空字符串填充:

Dim strTempPath As String
strTempPath = String$(144, vbNullChar)

當傳遞字符串到DLL函數中時,如果不知道字符串的長度,那么可以使用Len函數確定其長度。

獲取Windows臨時文件夾的GetTempPath函數,就是返回String值的DLL函數的例子。該函數接受兩個參數,一個空結尾的字符串變量和一個包含字符串長度的數值變量。修改該字符串以便包含路徑,例如C:\Temp\。(Windows需要一個臨時文件夾存在,於是該函數應該總是返回該文件夾的路徑。如果由於某種原因不存在臨時文件夾,GetTempPath返回0)。

下面的程序調用GetTempPath函數獲取Windows臨時文件夾的路徑:

Declare Function GetTempPath Lib "kernel32" Alias "GetTempPathA" _
(ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long
 
Property Get GetTempFolder() As String
'返回用戶臨時文件夾的路徑.
'對於根目錄,Windows需要一個臨時文件夾存在
'因此應該總是返回其路徑
'以防萬一,檢查GetTempPath的返回值
Dim strTempPath As String
Dim lngTempPath As Long
'使用空字符填充字符串
strTempPath = String(144, vbNullChar)
'獲得字符串的長度
lngTempPath = Len(strTempPath)
'調用GetTempPath,傳遞字符串長度和字符串
If (GetTempPath(lngTempPath, strTempPath) > 0) Then
'GetTempPath返回路徑到字符串中.
'截去字符串開始的空字符
GetTempFolder = Left(strTempPath, InStr(1, strTempPath, vbNullChar) - 1)
Else
GetTempFolder = ""
End If
End Property

注意,當傳遞字符串到函數中時,使用空字符填充該字符串。函數寫入返回的字符串值"C:\Temp"到字符串變量的第一部分中,並且剩下的保留空字符填充,接着使用Left函數截取字符串。

GetTempPath函數的實際返回值是已經被寫到字符串變量中的字符數。如果返回的字符串是"C:\Temp\",那么GetTempPath函數返回8。

注意,這僅對從函數返回字符串時傳遞空結尾字符串及其大小是必需的。如果函數不返回字符串到字符串參數中,而是接受對函數指定信息的字符串,那么只需傳遞正常的VBA字符串變量。

傳遞用戶定義類型到DLL函數

許多DLL函數需要通過使用預定義的格式傳遞數據結構。當從VBA中調用DLL函數時,根據函數的需求傳遞已經定義的用戶定義類型。

通過查看函數的聲明語句,您能夠理解什么時候需要傳遞用戶定義類型以及需要在代碼中包括哪種類型定義。需要數據結構的參數總是被聲明為長指針:指向內存中數據結構的32位數字值。為長指針參數約定的前綴是"lp"。此外,參數的數據類型是數據結構的名稱。

例如,看看GetLocalTime函數和SetLocalTime函數的聲明語句:

Private Declare Sub GetLocalTime Lib "kernel32" _
(lpSystem As SYSTEMTIME)
Private Declare Function SetLocalTime Lib "kernel32" _
(lpSystem As SYSTEMTIME) As Long

兩個函數都接受SYSTEMTIME類型的參數,即包含日期和時間信息的數據結構。下面是SYSTEMTIME類型的定義:

Private Type SYSTEMTIME
wYear As Integer
wMonth As Integer
wDayOfWeek As Integer
wDay As Integer
wHour As Integer
wMinute As Integer
wSecond As Integer
wMilliseconds As Integer
End Type

要將數據結構傳遞給函數,必須聲明SYSTEMTIME類型的變量,如下所示:

Private sysLocalTime As SYSTEMTIME

當調用GetLocalTime時,傳遞SYSTEMTIME類型的變量到該函數,並且使用表示當前本地的年、月、日、星期幾、小時、分、秒、毫秒的數字值填充該數據結構。例如,下面的Property Get程序調用GetLocalTime返回表明當前小時的值:

Public Property Get Hour() As Integer
'返回當前時間,然后返回小時
GetLocalTime sysLocalTime

Hour = sysLocalTime.wHour
End Property

當調用SetLocalTime時,也傳遞了SYSTEMTIME類型的變量,但首先提供數據結構的一個或多個元素的值。例如,下面的Property Let程序設置本地系統時間的小時值。首先,調用GetLocalTime函數獲取本地時間的當前值到數據結構中,然后使用傳遞給屬性過程的值更新數據結構的sysLocalTime.wHour的值。最后,調用SetLocalTime函數,傳遞相同的數據結構,包含通過GetLocalTime加新小時值而取得的值。

Public Property Let Hour(intHour As Integer)
'獲取當前時間以便所有值都是當前的
'然后設計本地時間的小時部分
GetLocalTime sysLocalTime
sysLocalTime.wHour = intHour
SetLocalTime sysLocalTime
End Property

GetLocalTime函數和SetLocalTime函數與GetSystemTime函數和SetSystemTime函數相似。主要的不同在於,GetSystemTime函數和SetSystemTime函數表達的時間為格林威治標准時間。例如,如果本地時間是午夜12時,而您居住在西海岸,那么格林威治標准時間就是上午8時,有8小時的時差。GetSystemTime函數返回當前時間即8:00 A.M,而GetLocalTime返回午夜12:00。

理解Any數據類型

一些帶有一個參數的DLL函數可以接受多個數據類型。在DLL函數的聲明語句中,這樣的參數被聲明為類型Any。VBA允許傳遞任何數據類型到這個參數。然而,DLL函數可能被設計為接受僅僅兩個或三個不同的數據類型,因此傳遞錯誤的數據類型可能會導致應用程序錯誤。

通常,當在VBA工程中編譯代碼時,VBA對傳遞給每個參數的值執行類型檢查。也就是說,確保傳遞的值的數據類型與函數定義中的參數的數據類型相匹配。例如,如果參數定義為Long型,而試圖傳遞String型的數值,則會發生編譯時錯誤。這適用於調用內置的VBA函數、用戶定義函數、或者DLL函數。當將參數聲明為類型Any時,不會進行類型檢查,因此當傳遞值到這種類型的參數時應該謹慎。

一些具有一個參數的DLL函數可以接受字符串或者指向字符串的空指針。指向字符串的空指針是一個特別的指針,指令Windows忽略所給的參數。它與零長度字符串("")不同。在VBA的早期版本中,程序員必須聲明參數為類型Any,或者聲明DLL函數的兩個版本,即一個版本定義參數類型為String,一個版本定義參數類型為Long。現在VBA包括vbNullString常量,代表指向字符串的空指針,這樣可以聲明參數為String類型,並且在需要傳遞空指針的情形下傳遞vbNullString常量。

獲取錯誤信息

DLL函數中發生的運行時錯誤的行為不同於VBA中的運行時錯誤,即沒有錯誤消息框顯示。當運行時錯誤發生時,DLL函數返回某值表時發生了錯誤,而且錯誤不會中斷VBA代碼的執行。

Windows API中的一些函數存儲運行時錯誤的錯誤信息。如果使用C/C++編程,可以使用GetLastError函數獲取關於發生的最后一次錯誤的信息。然而,從VBA中,GetLastError函數可能返回不確切的結果。要從VBA獲得關於DLL錯誤的信息,可以使用VBA的Err對象的LastDLLError屬性。LastDLLError屬性返回發生的錯誤號。

為了使用LastDLLError屬性,需要知道與錯誤相對應的錯誤號。在Win32API.txt文件沒有這方面的可用信息,而Microsoft Platform SDK中可以找到。

下面的示例展示在已經調用了Windows API中的函數后如何使用LastDLLError屬性。PrintWindowCoordinates程序接受窗口句柄,並調用GetWindowRect函數。GetWindowRect使用組成窗口的矩形的邊的長度填充RECT數據結構。如果傳遞了無效的句柄,將發生錯誤,並且可以通過LastDLLError屬性獲得錯誤號。

Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Long, _
lpRect As RECT) As Long
 
Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
 

Const ERROR_INVALID_WINDOW_HANDLE As Long = 1400
Const ERROR_INVALID_WINDOW_HANDLE_DESCR As String = "無效的窗口句柄."
 
Sub PrintWindowCoordinates(hwnd As Long)
'以像素為單位打印窗口左側,右側,頂部和底部位置
Dim rectWindow As RECT
'傳遞窗口句柄和空的數據結構
'如果函數返回0,那么錯誤就發生了
If GetWindowRect(hwnd, rectWindow) = 0 Then
'因為傳遞了無效的句柄
'所以如果發生錯誤則檢查LastDLLError並顯示對話框
If Err.LastDllError = ERROR_INVALID_WINDOW_HANDLE Then
MsgBox ERROR_INVALID_WINDOW_HANDLE_DESCR, _
Title:="錯誤!"
End If
Else
Debug.Print rectWindow.Bottom
Debug.Print rectWindow.Left
Debug.Print rectWindow.Right
Debug.Print rectWindow.Top
End If
End Sub

要獲得活動窗口的坐標,可以通過使用GetActiveWindow函數返回活動窗口的句柄,並將結果傳遞到前面示例定義的過程中。要使用GetActiveWindow函數,包括下面的聲明語句:

Declare Function GetActiveWindow Lib "user32" () As Long

輸入下面的過程后運行:

Sub test()
PrintWindowCoordinates (GetActiveWindow)
End Sub

要生成一條錯誤消息,隨便使用一個長整型數值調用這個過程。

參考資源:

David Shank,《Office VBA and the Windows API

Excel 2007 VBA參考大全

《Professional Excel Development》

《VBA and Macros for Microsoft Excel》

https://msdn.microsoft.com/en-us/library/aa201293(office.11).aspx

來自 <http://www.excelperfect.com/index.php/2009/07/15/usewindowsapi/>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM