MSDN Online Voices - Office Talk:Office VBA 和 Windows API
我们都知道,Visual Basic® for Applications (VBA) 是一种功能强大的编程语言,可用来开发自定义 Microsoft Office 解决方案。将 VBA 和一个或多个 Office 应用程序对象模块配合使用,您可以轻松地修改 Office 应用程序的功能,或者使两个或多个应用程序协同工作,从而完成单个应用程序无法完成的任务。VBA 只能控制操作系统的一小部分,即直接向 VBA 公开的那些函数和对象。Windows® 应用程序编程接口 (API) 提供了众多函数,可让您深入控制操作系统的绝大部分内容。您可以从 VBA 中调用 Windows API 函数,扩展和优化自定义 Office 解决方案。
在这个月的专栏中,我将为您简单介绍如何在 VBA 中使用 Windows API,并提供一些有用的示例。您可以复制这些示例,在自己的自定义解决方案中直接使用它们。
警告:调用 Windows API 和其他 DLL 函数可能会影响您的应用程序的可靠性。当您从自己的代码中直接调用 DLL 函数时,会跳过 VBA 在正常情况下提供的一些安全机制。如果您错误地定义或调用 DLL 函数(任何程序员都难免犯这类错误),就可能会产生应用程序错误,也称为常规保护错误,即 GPF。如果使用了 API,那么在运行代码前一定要保存项目,并确保已理解调用 DLL 函数的原理。
理解 API
简单地说,API 就是您用来控制组件、应用程序或操作系统的一组函数。API 通常包含一个或多个可以提供某些特定功能的 DLL。
DLL 是一些包含任何 Windows 应用程序都可以调用的函数的文件。在运行过程中,DLL 中的函数“动态地链接”到调用它的应用程序中。不管有多少个应用程序调用 DLL 中的函数,该函数只存在于驱动器上的一个单一的文件中,并且该 DLL 在内存中只创建一次。
您最常听说的 API 可能是 Windows API,它包含组成 Windows 操作系统的所有 DLL。每个 Windows 应用程序都直接或间接地与 Windows API 交互作用。Windows API 可以保证所有在 Windows 中运行的应用程序都按照统一的方式运行。
除了 Windows API 外,还有其他一些公开的 API。例如,邮件应用程序编程接口 (MAPI) 是用于编写电子邮件应用程序的一组 DLL。
传统上 API 是为那些创建 Windows 应用程序的 C 和 C++ 程序员编写的,但使用 VBA 也可以调用 DLL 中的函数。由于大部分 DLL 及其文档最初是为 C/C++ 程序员编写的,所以调用 DLL 函数和调用 VBA 函数可能会有所不同。为了使用 API,您需要了解如何向 DLL 函数传递参数。
为了调用 Windows API 中的函数,您需要参考有关文档,了解有哪些函数可供使用以及如何在 VBA 中声明和调用这些函数。以下是两个很有用的资源:
- 文件 Win32API.txt,包含在 Microsoft Office 2000 Developer 和 Microsoft Visual Basic 中。文件 Win32API.txt 包含了大部分 Windows API 函数所使用的 VBA Declare 语句。您可以使用 API Viewer 外接程序(同样包含在 Office 2000 Developer 中)来查找和复制您需要的 Declare 语句。有关安装和使用 API Viewer 外接程序的信息,请参阅 Office 2000 Developer 中的 apiload.txt 文件。Microsoft Visual Basic 附带的 API Viewer 应用程序具有相同的功能,只不过它是一个独立的应用程序。
初次运行 API Viewer 应用程序时,它将加载 Win32API.txt 文件。该文本文件可以导出到一个 Microsoft Access 数据库(.mdb 文件)中,这样能加速加载和浏览 API 数据的过程。
- Microsoft Platform SDK,包含完整的 Windows API 文档。它可以在 Microsoft Developer Network 站点上免费获得:http://msdn.microsoft.com/library/default.asp?URL=/library/psdk/portals/win32start_1n6t.htm(英文)。
使用 Declare 语句
在从 VBA 调用 DLL 中的函数前,您必须先告诉 VBA 该函数位于何处以及如何调用它。有两种方法可以实现这一目的:
- 设置 DLL 类型库的引用。
- 在模块中使用 Declare 语句。
设置 DLL 类型库的引用是使用 DLL 函数的最简单的方法。一旦设置了引用,您便可以调用 DLL 函数,就好象它是项目中的一部分。然而,这里有一些需要注意的地方。首先,设置引用多个类型库会影响应用程序的性能。其次,并非所有 DLL 都提供类型库。尽管可以设置引用一个不提供类型库的 DLL,但却不能象将其作为项目中的一部分那样调用该 DLL 中的函数。
请注意,组成 Windows API 的 DLL 不提供类型库,因而不能通过设置引用这些 DLL 来调用其中的函数。要调用 Windows API 中的函数,您必须在项目中的模块声明部分插入一条 Declare 语句。
Declare 语句是一条定义语句,它告诉 VBA 从何处获得特定的 DLL 函数以及如何调用该函数。在代码中添加 Declare 语句的最简单方法是使用 API Viewer 外接程序,该程序包含了大部分 Windows API 函数所使用的 Declare 语句,以及一些函数需要的常量和类型定义。
下例所示为 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 的 Declare 语句可将这些函数的参数定义为 Any 类型。将 DLL 函数的参数定义为 Any,调用时可能会有危险,因为 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 的名称包含在 Declare 语句的一个字符串中。如果在 Lib 关键字后指定了一个用户系统上不存在的 DLL,那么调用该函数时将导致运行时错误 48,“加载 DLL 时出错”。在 VBA 代码中可以处理这些错误,您可以编写具有适当错误处理的代码,使之运行稳定可靠。(如果您调用基本 Windows DLL 中的函数,这就不成为问题,因为当您加载应用程序时,就必然要用到这些 DLL。)
下表列出了 Windows API 中最常用的 DLL。
DLL | 包含的内容 |
Kernel32.dll | 低级操作系统函数,例如内存管理和资源处理函数 |
User32.dll | Windows 管理函数,例如用于信息处理、计时器、菜单和通信的函数 |
GDI32.dll | 图形设备接口 (GDI) 库(包含设备输出函数),例如用于绘图、显示环境和字体管理的函数 |
大部分 DLL,包括 Windows API 中的 DLL,都是用 C/C++ 编写的。向 DLL 传递参数前通常需要了解 C/C++ 函数所使用的参数和数据类型,它们与 VBA 函数要求的参数和数据类型有若干不同之处。
另外,很多参数通过赋值来传递给 DLL 函数。默认情况下,VBA 中的参数通过引用来传递。因此,当 DLL 函数要求通过赋值来传递参数时,必须在该函数定义中包含关键字 ByVal。如果在函数定义中省略关键字 ByVal,有些情况下可能会导致无效页面的错误。在另外一些情况下可能会发生 VBA 运行时错误 49,“DLL 调用约定错误”。
以引用方式传递参数会将该参数的内存地址传递给被调用的过程。如果被调用的过程改变参数的值,则将改变参数的唯一版本,因此当执行过程返回到主调过程时,该参数将含有更改后的值。
另一方面,以赋值方式向 DLL 函数传递参数是传递该参数的一个副本;函数将使用该副本来代替参数进行操作。这样可以避免函数更改真实参数的内容。当执行进程返回到主调过程时,参数包含的值与调用其他过程之前相同。
由于以引用方式传递参数可以在内存中修改参数值,因此如果不正确地以引用方式传递参数,DLL 函数可能会改写它不应该写入的内存,从而导致错误或其他一些难以预料的结果。Windows 中有很多不应该被改写的值。例如,Windows 给每个窗口分配一个唯一的称为“句柄”的 32 位标识符。因为如果 Windows 改变某窗口的句柄,就再也无法找到该窗口,所以句柄通常以赋值方式传递给 API 函数。(对于 String 类型参数,即使前面有关键字 ByVal,字符串也总是以引用方式传递给 Windows API 函数。)
使用常量
除了 Declare 语句,有些 DLL 函数还需要定义它用到的常量及类型。您应将常量、用户自定义类型的定义,和要使用它们的函数的 Declare 语句一起,放在模块的声明部分。
如何知道函数需要哪些常量和用户自定义类型呢?正如前面提到的那样,您需要参考函数的文档。文件 Win32API.txt 中包含了相关函数使用的常量和用户自定义类型。您可以使用 API Viewer 外接程序查找这些常量和用户自定义类型,并复制和粘贴到自己的代码中。不幸的是,常量和用户自定义类型与需要它们的 Declare 语句毫无关联,因此您仍需要查阅 DLL 函数的文档,以确定哪些常量和类型应和哪些 Declare 语句放在一起。
有时可能需要向函数传递常量,以指明您要求该函数返回何种信息。例如,GetSystemMetrics 函数接受 75 个常量之一,每个常量都指定操作系统的不同方面。函数返回什么信息取决于您向它传递什么常量。为了能够调用 GetSystemMetrics 函数,您不必包含所有 75 个常量,包含要使用的那些常量即可。
定义常量比直接传递它们所代表的值要好。Microsoft 保证在以后的版本中将保留相同的常量,但不保证常量值本身不发生变化。
DLL 函数所需常量的含义常常不是那么清楚,因此您需要查阅函数的文档,确定为了返回需要的值应该使用什么常量。
下面的示例包含 GetSystemMetrics 函数的 Declare 语句,以及它可以接受的两个常量,然后示范如何从内部属性过程中调用 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
使用句柄
在调用 DLL 函数前,您需要了解另一个重要概念:“句柄”。句柄是一个 32 位正整数,Windows 使用它来识别窗口或其他对象,例如字体或位图。
在 Windows 中,窗口可以是很多不同的内容。实际上,几乎所有在屏幕上能看得到的东西都在窗口中,而您看不到的东西大部分也都在窗口中。窗口可以是屏幕上的一个具有矩形边界的区域,就象您现在看惯了的应用程序窗口一样。尽管并非所有类型的控件都是窗口,但窗体上的一些控件也可能是窗口,例如列表框或滚动条。显示在桌面上的图标以及桌面本身都是窗口。
由于所有这些类型的对象都属于窗口,因此 Windows 可以采取类似的方式来处理它们。Windows 给每个窗口赋予一个唯一的句柄,并使用句柄来操作该窗口。很多 API 函数可以返回句柄或把它们当作参数。
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 函数可使用三个参数:窗口的句柄、以空字符结束的字符串(将从中返回窗口的标题)和字符串的长度。
调用函数
尽管调用 DLL 函数在很多方面与调用 VBA 函数相类似,但还是有些差别,您刚开始使用 DLL 函数时可能会有些疑惑。本节将讲述在 DLL 函数中如何定义参数类型及其前缀名,如何返回字符串、传递数据结构,将返回什么样的值,以及如何检索错误信息。
参数的数据类型
C/C++ 中的数据类型及其表示方法与 VBA 中的有所不同。下表描述了 DLL 函数中的一些常用数据类型和它们在 VBA 中的等价类型。
C/C++ 数据类型 | 匈牙利前缀 | 说明 | VBA 等价类型 |
BOOL | b | 8 位布尔值。零值表示 False;非零值表示 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 中使用的 Declare 语句。如果您在代码中使用这些 Declare 语句,则函数的参数已经定义为正确的 VBA 数据类型。
大部分情况下,只要您已经定义和传递了正确的数据类型,则调用 DLL 函数与调用 VBA 函数的效果完全相同。例外的情况将在后面讨论。
从 DLL 函数中返回字符串
DLL 函数与 VBA 函数返回字符串的方式有所不同。由于字符串总是按引用方式传递给 DLL 函数,因此 DLL 函数可以修改字符串参数的值。VBA 函数可以将一个字符串作为函数返回值,而 DLL 函数将字符串放在向函数传递的 String 类型的“参数”中返回。函数的实际返回值通常是一个长型整数,表示已写入字符串参数的内容的字节数。
接受字符串参数的 DLL 函数将获得一个“指针”指向该参数在内存中的位置。指针是表示字符串存储位置的内存地址。所以,在 VBA 中向 DLL 函数传递参数时,实际上是向 DLL 函数传递一个指向内存中字符串的指针。然后 DLL 函数修改存储在该地址中的字符串。
要调用 DLL 函数来填写 String 变量,您需要采取一些额外的步骤正确格式化该字符串。首先,String 变量必须是“以空字符结束的字符串”。这种字符串以一个特殊的空字符结束,空字符由 VBA 中的常量 vbNullChar 指定。
其次,一旦创建字符串,DLL 函数将无法改变它的长度。因此,必须确保传递给函数的字符串的长度足够容纳整个返回值。把字符串传递给 DLL 函数时,通常需要在另外一个参数中指定您传递的字符串的长度。Windows 将检查字符串的长度以确保不会改写该字符串所占用的任何内存。
向 DLL 函数传递字符串的好方法是创建 String 变量,并通过 String$ 函数用空字符填充该变量,以使其长度足以容纳函数将返回的字符串。例如,以下代码片断将创建一个长度为 144 字节的字符串,并用空字符填充。
Dim strTempPath As String strTempPath = String$(144, vbNullChar)
如果将字符串传递给 DLL 函数时不知道它的长度,可以使用 Len 函数确定其长度。
GetTempPath 函数是能够返回 String 值的 DLL 函数之一,它的功能是检索 Windows 临时文件夹的路径。它接受两个参数:空结束 String 变量和包含字符串长度的数字变量,并修改字符串以使其包含路径,例如“C:\Temp\”。(为了进行引导,Windows 要求存在一个临时文件夹,因此该函数应该总能返回临时文件夹的路径。如果由于某些原因未能返回路径,GetTempPath 函数将返回零值。)
以下过程将调用 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
图 1:向 DLL 函数传递的字符串
请注意字符串在传递给函数时填充了空字符。函数将返回的 String 值 (“C:\Temp\”) 写入到 String 变量的开头部分,其余部分用空字符填满,随后可以使用 Left 函数进行截断。
GetTempPath 函数的实际返回值是已写入到 String 变量中的字符的个数。如果返回的字符串是“C:\Temp\”,那么 GetTempPath 函数的返回值是 8。
请注意,只有从函数中返回字符串时才有必要传递以空字符结束的字符串及其长度。如果函数不是将字符串放在字符串参数中返回,而只是接受提供信息给函数的字符串,则可以使用常规的 VBA String 变量。
向 DLL 函数传递用户自定义类型
很多 DLL 函数要求使用预定义格式向它们传递数据结构。从 VBA 中调用 DLL 函数时,您需要按照函数的要求定义用户自定义类型,然后再向函数传递。
查阅函数的 Declare 语句,您就会知道什么时候需要传递用户自定义类型,以及需要在您的代码中包含哪种类型定义。要求数据结构的参数通常声明为“长指针”:一个指向内存中数据结构的 32 位数值。习惯上长指针参数的前缀是“lp”。另外,参数的数据类型将成为数据结构的名称。
例如,请看 GetLocalTime 函数和 SetLocalTime 函数的 Declare 语句:
Private Declare Sub GetLocalTime Lib "kernel32" _ (lpSystem As SYSTEMTIME) Private Declare Function SetLocalTime Lib "kernel32" _ (lpSystem As SYSTEMTIME) As Long
两个函数均接受类型为 SYSTEMTIME 的参数,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 函数将数据结构 sysSystem 设置为本地当前最新时间
。然后使用传递给属性过程的参数的值更新数据结构中的 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:00 的时候,格林威治标准时间是上午 8:00,有 8 个小时的时差。GetSystemTime 函数返回的当前时间为上午 8:00,而 GetLocalTime 函数返回的当前时间为晚上 12:00。
理解 Any 数据类型
有些 DLL 函数具有可使用多种数据类型的参数。在 DLL 函数的 Declare 语句中,这样的参数声明为 Any 类型。VBA 允许您给这种参数传递任何数据类型。然而,DLL 函数可能只接受两三种不同类型的数据,这样传递错误的数据类型可能会导致应用程序错误。
通常当您编译 VBA 项目中的代码时,VBA 检查每一个传递给参数的值的类型。也就是确保每个值的数据类型在传递时与函数定义中参数的数据类型相匹配。例如,如果您使用 Long 类型定义参数,但尝试传递一个类型为 String 的值,就会出现编译错误。不管您调用内部 VBA 函数、用户自定义函数还是 DLL 函数,以上情况都一样。然而,当您使用 Any 类型定义参数时,VBA 将不检查类型,因此向这类参数传递值时应该谨慎。
有些 DLL 函数具有可以接受字符串或者指向字符串的空指针的参数。指向字符串的空指针是一种特殊的指针,它让 Windows 忽略已给的参数。它不同于零长度字符串 ("")。在 VBA 的早期版本中,程序员必须将这样的参数声明为 Any 类型,或者声明两个不同版本的 DLL 函数,其中一个将该参数定义为 Any 类型,另一个定义为 Long 类型。现在 VBA 提供了 vbNullString 常量,它代表指向字符串的空指针,因此您可以将这类参数声明为 String 类型,在需要传递空指针时使用 vbNullString 常量即可。有关如何从 VBA 中传递空指针的详细信息,请在 Microsoft Developer Network(英文)站点上搜索关键字“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 语句:
Declare Function GetActiveWindow Lib "user32" () As Long
在立即窗口中,键入下列内容:
? PrintWindowCoordinates(GetActiveWindow)
为了生成错误消息,请用一个随机的长型整数调用该过程。
获取更多信息
要获得有关使用 Windows API 的更多信息,请访问以下资源:
- Microsoft Platform SDK,包含完整的 Windows API 文档。它可以在 Microsoft Developer Network 站点上免费获得:http://msdn.microsoft.com/library/default.asp?URL=/library/psdk/portals/win32start_1n6t.htm(英文)。
- 和往常一样,定期访问 Office Developer Center(英文),获取关于 Office 解决方案开发的信息和技术文章。
已存档的 Office Talk 专栏
2001 年 | ||
---|---|---|
4 月 1 日 | 探索 EXCELlent Office,第 2 部分(英文) | |
1 月 4 日 | 探索 EXCELlent Office(英文) | |
2000 年 | ||
12 月 7 日 | 又一个 Word(英文) | |
11 月 2 日 | Office 中的 Word | |
10 月 5 日 | 使用 Microsoft Access 中的工作表 (英文) | |
9 月 7 日 | 使用文件、文件夹和驱动器:更多的 VBA 提示和技巧(英文) | |
8 月 3 日 | 使用字符串和日期:VBA 提示和技巧(英文) | |
7 月 6 日 | 站在巨人的肩膀上(英文) | |
6 月 1 日 | 开发集成的 Office 解决方案,第 2 部分(英文) | |
5 月 4 日 | 开发集成的 Office 解决方案 | |
4 月 6 日 | 在 Microsoft Access 中运用组合框和列表框控件编程,第 2 部分(英文) | |
3 月 2 日 | 在 Microsoft Access 中运用组合框和列表框控件编程(英文) | |
2 月 3 日 | 在 Office 解决方案中进一步使用数据(英文) | |
1 月 13 日 | 在 Office 解决方案中使用数据(英文) | |
1999 年 | ||
12 月 9 日 | 使用 Office HTML(英文) | |
11 月 4 日 | 清扫办公室的邮袋(英文) | |
10 月 7 日 | 共享与类共享(英文) | |
9 月 2 日 | 单击此处安装(英文) | |
8 月 5 日 | 不是您母亲的办公室(英文) | |
7 月 8 日 | 利用暑假学习 Outlook(英文) | |
6 月 10 日 | Office 中的新内容(英文) |