Office版本变动
在office2010以前微软只提供了32-bit版本,但在2010以后出现了32-bit和64-bit两个版本的office。后者能处理更大的数据和VBA代码运行速度得到提升。代码的兼容性也变得复杂,特别是API的声明。
Office 2010以后有两个版本的office。64位的Excel能处理更大的数据,性能也得到提升。随着64位的引入同时也引入了新版的VBA即VBA 7。它能同时在两个版本运行。在安装office2010时,默认的安装版本是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中使用Delcare语句导入Dll函数。
Delare语句语法结构如下,看是否有返回类型:
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位内存中的系统上使用时,这种情况就被中断,因为32位可能会被截断或覆盖其他内存地址。这些情况可能会导致不可预测的行为或系统奔溃。
为了解决这个问题,VBA现在包含了一个真正的指针数据类型LongPtr。这个新的数据类型可以正确的编写API导入语句。
Declare PtrSafe Function RegOpenKeyW Lib "advapi32.dll" (ByVal hKey as LongPtr,ByVal lpSubKey as string,Byval phkResult as LongPtr) as Long
LongPtr数据类型和PtrSafe特性可以在32位和64位系统上正确声明API导入函数。PtrSafe特性向VBA编译器表明声明语句是真的64位Office的。如果没有这个特性在64位系统中Delcare语句将导致编译错误。注意PtrSafe语句在32位本质的Office中是可选的。下表提供了一些函数和数据类型数说明。
Type | Item | Description |
---|---|---|
修饰符 | PtrSafe | 指示声明API与64位兼容,在64位系统中是必须的。 |
Data Type | LongPtr | 不是一个真实的数据类型,在32位版本上是4个字节数据类型(Long),在64位版本上是8个字节的数据类型(LongLong)。这是VBA 7以后声明指针和句柄推荐的方法,它只在32位和64位的VBA7中得到支持。 |
Data Type | LongLong | 8个字节的数据类型,仅在64位Office中可用。 |
Conversion Operator | CLngPtr | 将表达式转换为LongPtr类型。 |
Conversion Operator | CLngLng | 将表达式转换为LongLong类型。 |
Function | VarPtr | 变体类型地址转换,在64位中返回LongPtr,在32位中返回Long数据类型。 |
Function | ObjPtr | 对象地址转换,在64位中返回LongPtr,在32位中返回Long数据类型。 |
Function | StrPtr | 字符串地址地址转换,在64位中返回LongPtr,在32位中返回Long数据类型。 |
注意:没有PtrSafe特性的API声明语句被认为与64位的Office不兼容。
综上所述,有两个条件编译常量:VBA7和Win64。为了确保与以前版本的Office向后兼容,可以使用VBA7常量来防止早期版本中使用64位代码。对于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 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
用这些函数来返回字符串类型、变体类型和对象类型的的指针(内存地址)。
动态链接库基础知识
A dynamic-link library(DLL)是一个包含函数和数据的模块,可以被另外一个模块或应用程序使用。
DLL定义两种函数:导出函数和内部函数。导出函数可以被其它应用程序使用,内部函数仅在内部使用。
Windows API就是一组Dll的集合,各Dll模块导出了不同的函数供外部模块使用。
每个加载Dll的进程都将Dll映射到它的虚拟内存地址空间中,在进程将Dll加载到它的虚拟地址之后,改进程就可以调用Dll的导出函数。
VBA声明API
先在微软官网上找到API函数原型,然后根据VBA语法规则导入函数。如下函数:
int MessageBox(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType
);
API函数都是C语言格式定义的,如果是指针类型则定义为VBA的长整数,如果是二级指针,则定义为Any类型。在office 2010 以后为了兼容32位和64位指针类型定义位LongPtr。
API函数一般又ASCII和Unicode两个版本,别名指定一个确定的版本,建议用Unicode版本,W结尾的API函数。
Office 2010 以后定义方式:
'选择VBA变量和C语言字节一致的变量,如 C的int是4Byte等同于VBA的long。VBA7以后多了一个LongPtr类型,在32位为Long类型在64位为LongLong类型。
#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。
可以加群共同交流:794568082。