首页 > 代码库 > 在VBA中使用Windows API

在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/>

在VBA中使用Windows API