注:本文为 “VBA API 概述 | 宏编程 | 执行速度慢” 相关文章合辑。
未整理去重。
VBA API 详解
Office 二次开发于 2020-12-17 22:27:10 发布
Office 版本变动
在 Office 2010 之前,微软仅提供 32-bit 版本的 Office。而自 Office 2010 起,出现了 32-bit 和 64-bit 这两个版本。64-bit 版本的 Office 具备一些优势,比如能够处理更大的数据量,并且 VBA 代码的运行速度也有所提升。不过,其代码的兼容性变得复杂了,尤其是在 API 的声明方面。
Office 2010 的 64 位版本引入了 VBA 7,它能够同时在 32 位和 64 位这两个 Office 版本中运行。在安装 Office 2010 时,默认会安装 32 位版本,若用户需要安装 64 位版本,则要手动进行相应选择。
对于 VBA 7 而言,若要让以前版本的 API 在 64 位版本中正常运行,必须重新对其进行声明,更新语句里所使用的地址指针以及窗口句柄的声明。
Windows 系统版本
64 位的 offic 能引用更大的内存地址空间,比以往使用更多的物理内存。除了引用应用程序用于存储数据或存储代码指令(指针)外,还可以用地址来引用显示的窗口句柄。根据使用是 32 位或 64 位系统来决定指针或句柄的大小(以字节位单位)。
运行 64 位的 Office 时,将面临两个基本问题:
- 在 office 中的本机 64 位进程不能加载 32 位二进制文件。当使用 ActiveX 控件和现有插件时,将不兼容。
- VBA 7 以前没有提供指针数据类型,如果开发人员使用 32 位变量来存储指针或句柄时,使用 Declare 定义的 API 返回的 64 位的值时部分值被丢弃。
VBA7 代码变化
VBA7 是一个新的代码库,它取代了早期的 VBA 版本,它适用于 32 位和 64 位的 Office。它提供了两个条件编译常数:VBA7 和 Win64。
VBA7 常数:
判断应用程序是否使用 VBA7 或者以前版本的 VBA,确保代码的向后兼容性。
Win64 常数:
判断代码是 32 位或 64 位的形式运行。
ActiveX Control 和 COM Add-in 兼容性
现有的 32 位 ActiveX 控件(包括第三方和微软提供的)与 64 位版本的 Office 不兼容。对于 ActiveX 控件和 Com 对象,有三种可能的解决方法:
- 如果有源代码,可以生成 64 位版本。
- 联系供应商更新。
- 寻找其它解决方案。
Office 中本机 64 位进程不能加载 32 位二进制文件。包括 MSComCtl 通用控件(TabStrip, Toolbar, StatusBar, ProgressBar, TreeView, ListViews, ImageList, Slider, ImageComboBox)和 MSComCt2 控件(Animation, UpDown, MonthView, DateTimePicker, FlatScrollBar),这些控件由以前 Office 或现在 32 位 office 安装,当迁移到 64 位 Office 中时,必须替换现有的控件。64 位 Office 不提供 64 位通用的控件。
Windows API 接口兼容性
VBA 类型库提供了许多功能,但有时候必须直接与计算机的操作系统和其他组件通信,例如管理内存和进程、使用用户界面或修改注册表。在这些使用场景中,最好的选择是嵌入动态链接库(DLL)的导出函数。在 VBA 中,可以通过 Declare
语句导入 DLL 函数。
Declare
语句语法结构如下,注意是否有返回类型:
Public/Private Declare Sub SubName Lib "LibName" Alias "AliasName" (argument list)
Public/Private Declare Function FunctionName Lib "LibName" Alias "AliasName" (argument list) As Type
SubName
或FunctionName
是 DLL 中的导出函数名。- 如果要确定调用 ASCII 或 Unicode 版本的 API,可以指定别名(
AliasName
)。 Lib
后面紧跟的是函数所在的 DLL 名称。- 参数列表必须与 DLL 中的导出函数一致。
下面的 API 函数在 Windows 注册表中打开一个子键并替换它的值:
Declare Function RegOpenKeyA Lib "advapi32.dll" (ByVal Key As Long, ByVal SubKey As String, NewKey As Long) As Long
该函数位于 Windows API 动态链接库 advapi32.dll
中,其函数名称为 RegOpenKeyW
。其原型定义如下:
LSTATUS RegOpenKeyW(HKEY hKey, LPCWSTR lpSubKey, PHKEY phkResult);
在 C 和 C++ 中,该 API 可以针对 32 位和 64 位进行编译。这是因为 HKEY
被定义为一个指针,它能正确反映编译代码所在平台的内存大小。
在早期的 VBA 版本中,没有特定的指针数据类型,因此使用了 Long
数据类型。Long
类型是 32 位的,在 64 位系统上使用时,可能会被截断或覆盖其他内存地址,从而导致不可预测的行为或系统崩溃。
为了解决这个问题,VBA 现在引入了一个真正的指针数据类型 LongPtr
。这个新的数据类型可以正确编写 API 导入语句:
Declare PtrSafe Function RegOpenKeyW Lib "advapi32.dll" (ByVal hKey As LongPtr, ByVal lpSubKey As String, ByRef phkResult As LongPtr) As Long
LongPtr
数据类型和 PtrSafe
特性
LongPtr
数据类型和 PtrSafe
特性可以在 32 位和 64 位系统上正确声明 API 导入函数。PtrSafe
特性向 VBA 编译器表明声明语句适用于 64 位 Office。如果没有这个特性,在 64 位系统中 Declare
语句将导致编译错误。
下表提供了一些函数和数据类型说明:
Type | Item | Description |
---|---|---|
修饰符 | PtrSafe |
指示声明的 API 与 64 位兼容。在 64 位系统中是必须的。 |
数据类型 | LongPtr |
不是一个真实的数据类型。在 32 位版本上是 4 字节的 Long 类型,在 64 位版本上是 8 字节的 LongLong 类型。这是 VBA 7 以后声明指针和句柄推荐的方法,仅在 VBA 7 中得到支持。 |
数据类型 | LongLong |
8 字节的数据类型,仅在 64 位 Office 中可用。 |
转换操作符 | CLngPtr |
将表达式转换为 LongPtr 类型。 |
转换操作符 | CLngLng |
将表达式转换为 LongLong 类型。 |
函数 | VarPtr |
返回变体类型的地址。在 64 位中返回 LongPtr ,在 32 位中返回 Long 。 |
函数 | ObjPtr |
返回对象的地址。在 64 位中返回 LongPtr ,在 32 位中返回 Long 。 |
函数 | StrPtr |
返回字符串的地址。在 64 位中返回 LongPtr ,在 32 位中返回 Long 。 |
注意:在 32 位版本的 Office 中,PtrSafe
是可选的。没有 PtrSafe
特性的 API 声明语句被认为与 64 位的 Office 不兼容。
条件编译常量
为了确保与早期版本的 Office 向后兼容,可以使用以下两个条件编译常量:
VBA7
:用于防止早期版本中使用 64 位代码。Win64
:用于区分 32 位和 64 位版本的代码。例如,一个 API 在 64 位版本中使用LongLong
,而在 32 位版本中使用Long
,则可以使用Win64
常量。
示例代码
#If Win64 Then
Declare PtrSafe Function MyTestFunc Lib "DllName" (ByVal N As LongLong) As LongLong
#else
Declare Function MyTestFunc Lib "DllName" (ByVal N As Long) As Long
#End If
#If VBA7 Then
Declare PtrSafe Sub MessageBeep Lib "User32" (ByVal N As Long)
#else
Declare Sub MessageBeep Lib "User32" (ByVal N As Long)
#End If
总结:
- 如果编写 64 位代码且希望与早期 Office 兼容,使用
VBA7
条件编译常量。 - 如果编写 32 位 Office 代码,代码工作原理与以前版本 Office 一致,无需条件编译常量。
- 如果希望编写兼容 32 位和 64 位 Office 的代码,使用
Win64
编译常量。
使用 StrPtr
、VarPtr
、ObjPtr
这些函数用于返回字符串类型、变体类型和对象类型的指针(内存地址)。
动态链接库基础知识
动态链接库(DLL)是一个包含函数和数据的模块,可以被其他模块或应用程序使用。DLL 定义两种函数:
- 导出函数:可以被其他应用程序使用。
- 内部函数:仅在 DLL 内部使用。
Windows API 是一组 DLL 的集合,各 DLL 模块导出了不同的函数供外部模块使用。每个加载 DLL 的进程都会将 DLL 映射到它的虚拟内存地址空间中。在进程将 DLL 加载到它的虚拟地址后,该进程就可以调用 DLL 的导出函数。
VBA 声明 API
在 VBA 中声明 API 函数时,需要先在微软官网上找到 API 函数原型,然后根据 VBA 语法规则导入函数。例如:
int MessageBox(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
API 函数通常是用 C 语言格式定义的。如果是指针类型,则在 VBA 中定义为 LongPtr
;如果是二级指针,则定义为 Any
类型。从 Office 2010 开始,为了兼容 32 位和 64 位指针类型,定义为 LongPtr
。
API 函数通常是用 C 语言格式定义的。在声明时,需要选择与 C 语言字节一致的 VBA 变量类型。例如,C 的 int
是 4 字节,等同于 VBA 的 Long
。从 VBA 7 开始,引入了 LongPtr
类型,用于兼容 32 位和 64 位指针类型。在 32 位系统中,LongPtr
是 Long
类型;在 64 位系统中,LongPtr
是 LongLong
类型。
API 函数通常有 ASCII 和 Unicode 两个版本。建议使用 Unicode 版本(以 W
结尾的 API 函数)。
Office 2010 及以后的定义方式
#If VBA7 Then
Public Declare PtrSafe Function MessageBox Lib "user32.dll" Alias "MessageBoxW" _
(ByVal hWnd As LongPtr, ByVal lpText As String, ByVal lpCaption As String, ByVal uType As Long) As Long
#Else
Public Declare Function MessageBox Lib "user32.dll" Alias "MessageBoxW" _
(ByVal hWnd As Long, ByVal lpText As String, ByVal lpCaption As String, ByVal uType As Long) As Long
#End If
上面的代码定义了一个 Unicode 版本的 API,同时兼容各个版本的 Office。
一文讲透 VBA 32 位和 64 位的 API 声明
ivccav 发表于 2023 - 3 - 17 16:57 最后编辑于 2023 - 3 - 17 16:59
从 Office 2010 开始,分 32 位和 64 位两个版本可供安装,同时 VBA6 升级到 VBA7。Office2010 之前的版本只有 32 位,没有 64 位。
在 32 位平台上的 API 函数中指针和句柄都是 32 位,由于 VBA6 和早期版本中没有指针或句柄的特定数据类型,可使用 Long 数据类型(4 字节)代表指针和句柄,相安无事。但到了 64 位环境,指针和句柄升级到 64 位(8 字节),64 位数据无法保存在 32 位数据类型中,当 64 位 API 函数使用或者返回指针和句柄时,会被截断,造成的后果是内存溢出、代码异常,甚至应用程序崩溃。
为了解决此问题,并使 VBA 代码能够在 32 位和 64 位环境中都能正常工作,VBA7 中新增了 2 种数据类型 LongPtr 和 LongLong ,和一个声明关键字 PtrSafe:
-
LongPtr:指针数据类型。LongPtr 会根据 Office 版本进行不同解析,32 位版本的 Office 中解析为 Long,64 位版本的 Office 中解析为 LongLong。
LongPtr 只能用于 VBA7 中(Office2010 开始 VBA 版本才升级到 VBA7),用来代表指针和句柄。
-
LongLong:带符号的 64 位整数。该数据类型只能在 64 位版本的 Office 中使用。在需要使用非常巨大的数据时可使用 LongLong 类型。
-
PtrSafe:指针安全关键字。PtrSafe 关键字只能用在 Declare 声明语句中。
说是指针安全,但并不会真的安全,API 函数中所有 64 位的参数和接收返回值,都必须重新定义为 64 位。
以上 3 个新功能无法在 VBA6 及其之前版本中使用。
想要代码在 32 位和 64 位环境下通用,可以使用条件编译。
条件编译的语法和 if 语句一样,只在 if、else、elseif 和 end if 关键字前面加“#”,下面举例说明:
先看一段代码:
#If VBA7 Then
Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
#Else
Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
#EndIf
关键字前面带“#”,表示条件编译,VBA7 是编译常数。
这段代码的意思是,如果 VBA 版本是 VBA7,编译 Declare PtrSafe Function…语句块,否则编译 Declare Function 语句块。
特别强调的是,VBA7 常数不代表 Office 版本是 32 位或 64 位,仅仅指示 VBA 的版本是第 7 版。
在 VBA7 中,LongPtr 数据类型可自动解析为 32 位和 64 位数据类型,这取决于安装的 Office 版本。
例子中 hwnd 是句柄,定义为 LongPtr,VBA7 会根据 Office 版本自动解析为 32 位或者 64 位数据类型。
因为 LongLong 只能用在 64 位 Office 版本中,如果需要使用该数据类型,使用 #If VBA7 And Win64 Then(当 VBA7 和 Win64 都为 True 时,才能确定 Office 是 64 位版本),如:
#if VBA7 then ' 运行在 VBA7 中,可能是 32 位或 64 位
Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
#if Win64 then ' 运行在 64 位 Office
Dim i As LongLong
#else ' 运行在 32 位 Office
Dim i As Long
#end if
#else ' 运行在 VBA6 及其之前版本,只有 32 位
Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
#end if
条件编译可用于代码的任何位置,甚至是函数或过程中:
#If VBA7 Then
Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long
Declare PtrSafe Function GetClipboardData Lib "user32" Alias "GetClipboardDataA" (ByVal wFormat As Long) As LongPtr
#Else
Declare Function OpenClipboard Lib "user32" (ByVal hwnd As Long) As Long
Declare Function GetClipboardData Lib "user32" Alias "GetClipboardDataA" (ByVal wFormat As Long) As Long
#End If
Const CF_BITMAP = 2&
Sub test()
#If VBA7 Then
Dim hBmp As LongPtr
#Else
Dim hBmp As Long
#End If
If OpenClipboard(0) Then
hBmp = GetClipboardData(CF_BITMAP)
End If
End Sub
注意上例中,GetClipboardData 函数返回一个剪贴板上的位图句柄,需要根据不同 Office 版本,定义接收参数 hBmp,hBmp 存放句柄。
无论是 API 或函数的返回值,还是 API 或者自定义函数的参数,只要是 64 位数据类型,如句柄或指针,都需要重新定义,否则程序运行会异常。
所以需要了解 API 函数的参数或者返回值是什么,才能定义恰当的数据类型进行传递或接收。这可以查询互联网了解函数参数,也可以用 API 浏览。
【数据分析】Excel 中使用 VBA 进行宏编程
STARBLOCKSHADOW 已于 2024-05-19 19:35:55 修改
Microsoft Excel 是广泛用于数据处理和分析的工具,而 VBA(Visual Basic for Applications)是一种基于 Microsoft Visual Basic 的编程语言,可用于在 Excel 中创建自定义宏和自动化任务,控制 Excel 工作簿、工作表和数据,执行各种操作。这篇博客将介绍如何使用 VBA 进行 Excel 宏编程,以提高工作效率和自动化重复任务。
0 准备工作
- Excel 中主选项卡默认是没有开发工具选项的,需要选择主选项卡
文件→选项→自定义功能区
,打开 Excel 中的开发工具面板:
这样,在 Excel 的主选项卡中就有开发工具选项了:
- 在开发工具选项卡中选择
Visual Basic
,进入 VBA 编辑器(或使用快捷键Alt
+F11
):
- 创建新的模块(Module)并开始编写第一个宏:
-
如,在 Excel 中显示消息框:
Sub 显示消息框 () MsgBox "欢迎使用 VBA 宏编程!" End Sub
-
编写完成后,保存,使用
F5
运行或者在开发工具选项卡中选择 “运行” 来执行宏: -
运行结果:
1 VBA 简介
1.1 Excel VBA 应用程序的构成
从开发者角度看,Excel VBA 应用程序由工作表、用户窗体、模块和类模块等部分构成。
-
工作表:用于保存和显示程序的数据,是程序的主体部分。一般先在工作表中制作出特定表格的格式,并设置好样式,再通过 VBA 代码获取表格中的数据,经过加工处理后将其填写入相应的单元格,供用户进行查看、打印输出等操作。
-
用户窗体:在 Excel VBA 应用程序中,除了可使用工作表与用户进行交互外,还可向程序添加用户窗体,用来与用户进行交互操作。使用用户窗体可将用户与工作表中的数据进行隔离,防止数据被意外修改并隐藏工作表中的敏感数据,使限制权限的用户只看到应该操作的数据。
-
模块:在模块中可保存程序的通用过程,供其他过程调用。例如录制宏的代码就保存在模块中。
-
类模块:在 Excel VBA 中,除了可使用系统提供的对象外,还可通过自定义类来创建自定义的对象,自定义的类必须保存在 “类模块” 中。大多数应用程序都不使用 “类模块”。
1.2 事件驱动
VBA 是运行在 Microsoft Office 软件之上,包括 Excel、Word、PPT、Outlook 等,可以用来编写非软件自带的功能的编程语言,不同的是每一个软件具有自己独有的对象,例如 Excel 有单元格对象,Word 有段落对象,PPT 有幻灯片对象。
Office 软件提供丰富的功能接口,VBA 可以调用它们,实现自定义的需求。基本上,能用鼠标和键盘能做的事情,VBA 都能做,它采用了与 Windows 相似的事件驱动编程方式。
在这种模式下,Windows 监视窗口活动或事件信号,这些事件可以是用户鼠标点击或按键操作,也可以是程序控制或其他窗口的操作引发的。通过 VBA 的事件驱动机制,开发人员可以编写事件过程来处理系统产生的事件,实现特定的功能,如在鼠标点击时打开一个窗口。
与传统的过程化应用程序不同,事件驱动的应用程序中,代码执行路径不是按照预定顺序进行的。相反,代码在响应不同事件时执行不同的片段。这些事件可以由用户、操作系统、其他应用程序的消息触发,甚至是应用程序自身的消息触发。
由于事件顺序无法预测,代码必须对各种执行状态做出假设。为确保这些假设在执行时有效,应用程序的结构需要组织良好。在 Excel 中使用 VBA 开发应用程序实质上是编写处理各对象不同事件的代码。
1.3 宏
简单的说,宏就是一段可以运行的 VBA 代码片段。Excel 宏使用 VBA 语言进行编写,通过 VBA 编写的宏可控制 Excel,对 Excel 的功能进行扩充。
1.3.1 创建宏
Excel 提供了两种创建宏的方法:一种方法是利用 Excel 操作环境中的宏录制器录制用户的操作;另一种方法是使用 Visual Basic 编辑器编写自己的宏代码。
利用宏录制器可记录用户在 Excel 中的操作动作,以便自动创建需要的宏,这在不太了解宏命令时是非常方便的。
使用 Visual Basic 编辑器可以打开已录制的宏,修改其中的命令,也可以在 Visual Basic 编辑器中直接输入命令创建宏。对于很多无法录制的命令(如创建新的窗体等),使用 Visual Basic 编辑器创建宏是唯一的方法。
启用录制宏的两种方式: