韩建彬
(武警指挥学院,天津 300250)
随着硬件的更新和软件的升级,Windows已经进入了64位时代,与之相适应,自Office2010开始,也提供了64位的版本,并将其内置的VBA升级到了7.0。这虽然增强了对64位数据的支持,为应用程序性能的提升创造了条件,但同时也带来代码的兼容性问题。笔者曾在Excel 2003中编写过一个从题库中随机选题的VBA程序[1],在32位版本Office 2010中可以正常运行,但在64位版本中,会提示“编译错误:“若要在64位系统上使用,则必须更新此项目中的代码。请检查并更新Declare语句,然后用PtrSafe属性标记它们。”出现这样的情况要从64位系统的特性和VBA的改变来分析。
对于Windows来说,64位系统理论上能够使用更多的指令集,一次可以处理8个字节的数据,并拥有更大的寻址空间,从而突破了32位系统中4GB物理内存和每个进程2GB虚拟内存的限制[2],有利于充分发挥硬件的潜能。而在程序设计中,64位系统带来的最明显改变就是指针和用于标识窗口、设备、文件等对象的句柄都变成了64位,即8个字节的长度。如果使用Microsoft Spy++(X64版)对64位系统进行检查,就能直观地看到这一变化[3]。
具体到VBA7.0,可以同时支持32位和64位的应用,并且在32位版本的Office中,以前所编写的VBA代码无需作任何修改,完全可以正常运行。而在64位版本的Office中,VBA为了与系统的变化相协调,特别是针对指针、句柄及其他64位数据,增加了相应的关键字和数据类型[4]。如果在64位的VBA环境中,没有正确使用相应的关键字和数据类型,就会引发编译错误或不可预知的运行结果。
如果VBA代码中包含对Windows API的声明语名,在以前版本的VBA环境中,其声明语句的格式通常为Public/Private Declare Function FunctionName Lib"Libname"alias"aliasname"(argument list)As Type,但这仅适用于32的环境。在64位的VBA环境中,必须在Declare前面添加上PtrSafe关键字,以表明代码适用于64位环境,否则将不能进行编译。前面出现的编译错误提示,首先就是因为缺少了这一关键字。
仅在Windows API的声明语句中添加PtrSafe关键字,常常并不能彻底消除编译错误,因为还涉及到数据类型的匹配问题。如果在API的声明和调用中使用了句柄或指针,则必须修改声明中的参数类型。
在VBA 7.0之前版本中,没有针对指针和句柄的特定数据类型,通常使用Long数据类型(长度为4个字节)来定义指针和句柄。而在64位的环境中,指针和句柄变为8字节,不能直接转换为Long数据类型。为了解决这一问题,VBA 7.0中包含了真正的指针数据类型LongPtr,它在32位的Office 2010中解释为4字节的数据类型,在64位版本中则解释为8字节数据类型,因此,需要将声明语句中涉及句柄和指针的变量由Long类型修改为LongPtr类型。
这是在64位的Office 2010及以后的Office版本中所使用的8个字节的数据类型,用于定义64位整数。64位环境中的某些函数需要使用这一特定的数据类型作为参数或返回值的类型。LongLong与较短的整数类型之间不允许进行隐式转换,必须通过转换函数才能将LongLong(包括64位环境中的LongPtr)显式赋予32位的整数类型。
由上面的分析可知,要想让VBA代码能够运行于64位版本的VBA 7.0环境中,必须更新其中有关Windows API应用程序编程接口的声明。首先,要为所有的Declare语句添加PtrSafe关键字。其次,还需要修改这些Declare语句内所有句柄和指针的定义,以使用新的64位兼容的LongPtr类型。第三,需要使用新的LongLong数据类型保存64位的整数,包括用户自定义类型中所含有的指针、句柄以及64位整数,使其真正符合系统对数据类型的要求。第四,在函数调用过程中,必须验证所有变量的赋值是否正确,以防因类型不匹配而引发编译或运行错误。
在VBA 6.0环境下,声明Windows API函数的具体语句包含在微软提供的Win32API.TXT文件中,而在VBA 7.0出现以后,微软又发布了Win32API_PtrSafe.TXT文件,提供了有关函数在VBA 7.0中的声明语句。
例如,在Office 2003中,借助SetTimer函数实现随机选择题目的程序,使用了如下的声明语句:
而在Office 2010的64位版本中,则需要改写成如下格式:
从两者的对比可以看出,在Function前面添加了“PtrSafe”关键字,并且其中的窗口句柄、定时器ID、回调函数指针都换成了LongPtr类型,这样才能在64位环境中运行。
再如,用于引用程序主窗口句柄的FindWindow函数,在Office 2003中的声明形式为:
而在Office 2010的64位版本中则要修改为:
在添加了PtrSafe关键字的同时,函数的返回值,即窗口句柄被修改为LongPtr类型。而用于接收函数返回值的变量,也必须相应地由Long类型修改为LongPtr类型。
另外,程序中还用到了KillTimer等函数,其声明语句也需要做类似的修改,具体请在微软提供的两个文件中自行查阅和比较。
通过以上的修改,原有的代码虽然可以在VBA7.0中运行,但在VBA 6.0及以前的版本中,则会因为新关键字的引入而无法编译,为了保证代码的兼容性,需要利用VBA的条件编译功能。
在VBA7.0中,提供了两个用于条件编译的常量:VBA7和Win64。VBA7用于测试当前Office中所内置的VBA的版本,如果是最新的VBA7版本则为True,如果是以前的版本,则为False。通过VBA7这一常量,我们就能确定VBA程序的运行环境,以便按照具体的环境编译相应的语句,从而保证代码的兼容性。
在MSDN中,常量Win64被解释为“用于测试代码是以32位还是64位形式运行的”,这样的表述并不是很明确,其中的“代码”具体指运行环境中的哪一个层次,是操作系统还是Office软件?通过在不同的运行环境中进行实验,其结果如下:在64位的Windows系统中,使用Office 2010及2013进行测试,如果是32位的版本,“Win64”为False,“VBA7”为True;如果是64位的版本,“Win64”为True,“VBA7”为True。由此可见,“Win64”主要表示当前的Office是否为64位,而“VBA7”则表示当前的VBA环境是否为VBA7.0。
根据MSDN中的说明,VBA 7.0可以同时支持32位和64位的程序代码,且32位Office中的VBA7.0与以前的VBA完全兼容,只有64位版本Office中的VBA7.0才会产生代码不兼容的问题。因此,可以使用如下的条件编译语句:
例如,为了存储FindWindow函数的返回值,声明变量hWnd,其条件编译代码为:
这样就能保证变量声明在32位和64位环境中的兼容性。
而对于某些在64位和32位的VBA 7.0环境中也具有不同声明要求的函数,其条件编译要使用嵌套结构,需同时使用Vba7和Win64作为编译条件。如WindowFromPoint函数,由于在不同的环境中参数的类型不同,需要使用如下的编译条件。
虽然以上的结构还可以简化,但为了使代码中的逻辑关系更为清晰,仍建议采用这样的形式。
对于Office来说,32位与64位版本共存的状况必然还要持续一定的时间,VBA也仍将是Office开发中的重要工具,为了提高VBA代码的兼容性,必需根据运行环境的发展变化,按照MSDN中的说明来设置编译条件,并进行相应的测试。此外,由于64位的进程中不能加载32位的ActiveX控件,因此,如果VBA中引用了以前版本的Office所提供的32位控件,如MSComCtl、MSComCt等,那么在64位版本的Office中是无法运行的,还需要进一步研究可行的替代方法。
参考文献(References):
[1]韩建彬.Windows API在VBA编程中的应用研究[J].计算机时代,2012.240(6):13-14
[2]Memory Limits for Windows and Windows Server Releases[EB/OL].https://msdn.microsoft.com/library/aa366778.aspx.
[3]Spy++简介[EB/OL].https://msdn.microsoft.com/zh-cn/library/vs/alm/dd460756(v=vs.140).aspx.
[4]64位Office 2010版本.https://technet.microsoft.com/zh-cn/library/ee681792(v=office.14).aspx.