[摘要]摘要 这篇技术文章探讨如何编写Microsoft Office解决方案代码,以便使用16位和32位版本的Office产品顺利进行应用编程接口(API)调用。特别是,本文适用于Microsoft Access 、Visual Basic 、Microsoft Word 、Microsoft Exce...
摘要
这篇技术文章探讨如何编写Microsoft Office解决方案代码,以便使用16位和32位版本的Office产品顺利进行应用编程接口(API)调用。特别是,本文适用于Microsoft Access 、Visual Basic 、Microsoft Word 、Microsoft Excel 和Microsoft Project 。实现这种API调用的方法有三类:使用REGISTER、使用Declare语句和使用一个类型库。本文分别对这三种API调用方法进行了探讨和举例。在本文中,假设读者已对我先前的一篇技术文章“将你基于16位Office的解决方案移植到32位Office ”较为熟悉,并且假设读者是一位经验丰富的Office 开发者,他需要移植或编写解决方案,以满足16位到32位互操作性的新要求。
引言
对于编写一个面向多个平台多个版本Microsoft Office 产品的解决方案问题,它是大多数编译语言开发者所不熟悉的。编译语言应用程序开发者喜欢在编辑程序开发中选择一个时间段,将解决方案加到一个执行文件中,而很少需要解决由产品开发引起的问题。一个Office 解决方案应具有一个可在所有Office 产品和所有操作系统中正确执行的解决方案文件(一个.XLS、.MDB、.MPP或.DOT文件)。Office 开发者必须编写具有对前五年内发布的版本的向后兼容性和对将发布的下一版本的向前兼容性。例如,一个在Microsoft Excel 4.0和Microsoft Excel 5.0上运行的费用报告应能在Microsoft Excel NT、Microsoft Excel 95和Microsoft Excel 2001(假定存在)上运行。
Office 95将32位Office 产品引入到主流企业环境中,它们将与16位Office 产品共存许多年。在我以前的一篇技术文章“将你基于16位Office的解决方案移植到32位Office ”中,对不能从一个32位应用程序调用16位API的问题以及不能从一个16位应用程序调用32位API的问题进行了阐述,在那篇文章中列出的16位/32位互操作性解决方案将在本文中作进一步的探讨。
注:根据经验,我把Windows 3.1中的API调用转换为Win32 应用编程接口(API)调用称之为32A规则,即:“如果在任何调用参数中使用了名称字符串,给DLL名添加一个32,给函数名添加一个A(对于ANSI)”。这个经验对于绝大多数API调用是可行的,但对于某些函数,这个经验可能行不通,因此它并不是绝对正确的解决方案。
16位/32位互操作性解决方案比16位API调用移植到相当的32位API调用(换句话说,使用32A规则)的要求更多;它需要解决方案在16位或32位Office 产品中执行时能选择适当的API。
术语
为了避免用语使用的混淆,下列表中定义了在本技术文章中使用的术语:
术语 定义
Office 产品 不将代码编译为一个可执行程序的产品。下列产品之一:Microsoft Project 、Microsoft Access、Word 和Microsoft Excel 。
编译语言 将代码编译为一个可执行程序的产品,例如Visual Basic 、FORTRAN、PowerStation 、Visual FoxPro 和Visual C++。
解决方案 由第三方或开发者使用某个Office 产品及其格式(如Microsoft Excel (.XLS)、Word (.DOT)、Microsoft Project (.MPP)或者Microsoft Access(.MDB))开发的应用程序。
解决方案代码 编写有解决方案的代码(XLM或Basic或者结合使用)
平台 Microsoft Windows和Apple Macintosh 操作系统。一些Office 产品适用于所有Windows和Macintosh操作系统。通过将解决方案从一个操作系统复制到另一操作系统, Microsoft Excel 可在Macintosh和Windows PC中执行同一应用程序(在一定的限制条件下)。
API调用方法
解决方案代码可采用下列三种方法之一进行编写--使用Microsoft Excel 宏(XLM)、Basic(Access Basic、Visual Basic 、VBA和WordBasic)或者混合使用XLM和Basic。XLM使用REGISTER和CALL命令来进行API调用,Basic使用Declare语句、类型库或者这两者的结合使用来进行API调用。经一个开发者传给另一开发者的Microsoft Excel 解决方案可以使用所有这些方法进行API调用。这三种方法对于最新版本的Microsoft Excel 都适用。
表1 API调用方法描述
方法 语言 进行API调用的方法
REGISTER XLM宏 从宏中使用REGISTER和CALL函数
Declare Access Basic、
Visual Basic 、
VBA、WordBasic
使用Declare语句
类型库 仅VBA 从对象浏览器上选择类型库方法
这些方法不会产生某个特定版本的解决方案,它取决于开发者编写代码的引用或解决方案所必须运行的产品范围。例如,为了使解决方案在Microsoft Excel 4.0中也有效,开发者可以在Microsoft Excel 95的XLM中编写一个解决方案。表2中列出了各种API调用可在哪些Microsoft 产品中使用。
表2 Microsoft 产品的API调用方法
产品版本 REGISTER方法 Declare方法 类型库方法
Microsoft Excel 3.0,4.0 X
Microsoft Excel 5.0,5.0NT,95 X X X
Word 2.0,6.0,95 X
Visual Basic 1.0,2.0,3.0 X
Visual Basic 4.0 X X
Project 4.0,95 X
Microsoft Access1.0,1.1,2.0 X
Microsoft Access95 X X
FoxPro 2.5 X
FoxPro 3.0 X X
我们在讨论16位和32位API调用时,都将使用同一个简单的API调用示例--GetTickCount来进行论述。
REGISTER方法
对于在编写Office 解决方案前先在Visual Basic 中编写过代码的开发者,他们并不熟悉使用REGISTER方法的API调用。旧版本Microsoft Excel 的开发者从XLM中进行API调用,并避免使用VBA。我们不能把XLM看作是过时的技术--虽然从Microsoft Excel 4.0开始未对其更改。如果性能问题变得很重要时,XLM仍是最好的选择。Microsoft Excel 5.0开发者工具包中陈述了直接从XLM中进行API调用的优点。
由于C API最易于在Microsoft Excel 宏和工作表上使用,因此对于Visual Basic 所用的外部函数的编写,它并不是非常好(虽然VB 和C API可被融入混合解决方案中)。
为了从XLM进行API调用,要求使用REGISTER函数来引用动态连接库(DLL)以及使用CALL函数来执行它。REGISTER函数与VBA中的Declare语句的作用是一样的。推荐的REGISTER句法如下:
REGISTER(module_text,procedure,type_text,function_text,argument_text,macro_type,category,shortcut_text)
要返回到所给出的API调用示例,我们可以在XLM的一些代码行中采用GetTickCount。
这个代码在16位Microsoft Excel 中产生下列输出:
如果我们在32位的Microsoft Excel 中运行这个代码,结果是失败的。API调用返回#VALUE!,表明传递给DLL或从DLL中传递变量是失败的:
我们必须将这个代码转换为一个32位的API调用,因此,我们使用32A规则,在DLL名上添加一个32,如果是字符串,在其上添加一个A。采用32A规则的代码如下所示。
在调用TestGetTickCount32A时,我们会遇到另一种错误。返回#NAME?变量,这意味着在DLL中不存在这个函数。它可能是错误的DLL或错误的函数名(记住,函数名是区分大小写的)。这必定是32A规则的一个例外(我承认,我是有意选用这个函数的)。
在MSDN库中,使用关键字索引在PlatformSDK(平台软件开发包)中查找GetTickCount。在主题的顶端弹出的快速信息(Quick Info)会告诉你该函数在KERNEL32库中(在Window 3.x中,该函数在USER库中)。下列代码列出修改后的32位Microsoft Excel 宏。
在32位Microsoft Excel 中运行此代码,我们得到正确的结果:
为说明在16位和32位Microsoft Excel 中的API调用方式,参阅表3,表中显示了这些宏的结果。使用32A规则的宏(“32A位”)在16位和32位解决方案中产生的结果相同。它说明了REGISTER函数从16位或者32位解决方案中定位(或定位失败)16位和32位DLL中的函数名的能力。返回#VALUE!表明在传递或接收参数中出现了问题。
表3:从16位和32位Microsoft Excel 进行调用的结果
Microsoft Excel 4.0(16位版) Microsoft Excel 95(32位版)
我们能够编写宏以进行16位API调用或32位API调用,但是,我们必须编写可进行16位API调用和32位API调用的宏,这取决于Office 产品的版本。解决方案代码必须在16位Microsoft Excel 和32位Microsoft Excel 上均能运行。在我以前的文章(“将你基于16位Office的解决方案移植到32位Office ”)中描述的VBA函数Engine32不能在Microsoft Excel 4.0(Microsoft Excel 4.0不包含VBA)中正常工作。Engine32可在Microsoft Excel 5.0及更高版本上正常工作。一个与Microsoft Excel 4.0兼容的Engine32函数必须使用宏代码。
XLM的Engine32函数
对于XLM,解决方案与VBA解决方案类似。如果Microsoft Excel 是一个32位版,函数信息(“osversion(操作系统版本)”)将包含32。如果Microsoft Excel 是一个32位版,下列所示的Engine32宏返回TRUE;如果Microsoft Excel 是16位版,该宏返回FALSE。
REGISTER方法解决方案的示例
由于Enging32已定义,GetTickCount的宏代码很简单:
Engine32将在所有版本的Microsoft Excel 中运行正常。GetTickCount函数与在Windows3.1中的API调用是一样的。
注:如果对性能关心,你应在装载这个解决方案时注册所有API调用。
REGISTER方法解决方案的步骤
如果你正将Microsoft Excel 解决方案转换为能在16位和32位产品上运行的解决方案,我建议你采用下列步骤:
创建一个名为APICALLS的新宏;
在APICALLS宏中创建Engine32函数;
定位解决方案中的所有REGISTER函数,并将它们移到APICALLS中,一个一栏;
使用上面的REGISTER解决方案示例中的宏代码作为一个模板,在你的宏表中为每个API创建函数;
添加所需的ARGUMENT行;
添加所需的RESULT行;
添加16位API REGISTER行,在函数名后加上16;
添加32位API REGISTER行,在函数名后加上32;
进行任何数据处理以进行API调用;
添加一个IF行以调用相应的API;
RETURE返回变量(如果有);
测试函数;
为Microsoft Excel 定义函数。
这个过程可使宏表中现有的API调用保持原样。一旦你创建了宏表APICALLS(并进行了测试),你可以把它加到其他解决方案中,重复使用这个宏,这样可节省转换时间。该宏表的内容独立于解决方案(它仅包含Windows API调用)并可在其他解决方案中重复使用。这个宏表APICALLS成为将来开发16位/32位解决方案的罗塞达石碑。
Declare方法
VBA的引入给Microsoft Excel 开发者提供了XLM的替代方法。Visual Basic 、WordBasic和Microsoft Access开发者也能毫无困难地编写Microsoft Excel 和Microsoft Project解决方案。Basic代码可在广泛的产品中进行交换。年轻一代的Office 开发者将在VBA中编写代码,而很少使用XLM。
从VBA中调用API需要使用API Declare语句。一个经声明的API可在Basic代码(或在一个宏中)的任何地方被调用。编写Declare语句有两种方法,如下所示:
Word Declare语句
注释:Word是首选的编写Declare 语句的方法
注释:它的格式是固定的,具有向后兼容性。
Declare Sub SubName Lib LibName$ [(ArgumentList)] [ Alias Routine$]
Declare Function FunctionName[$] Lib LibName$ [( ArgumentList)] [Alias Routine$] As Type
VBA、Basic和Microsoft Access Declare语句
Declare Sub globalname Lib "libname" [Alias "aliasname" ][([ argumentlist])]
Declare Function globalname Lib libname [Alias aliasname ] [([ argumentlist])] [As type]
因为我已在“将你基于16位Office的解决方案移植到32位Office ”中进行了这方面的一些讨论,并得出了上面的分类,我将简要地利用一个函数示例来说明Declare解决方案。
Basic Engine32函数
如果32位API调用是正常的(16位API调用失败),Engine32函数返回True;如果16位API调用是正常的(32位API调用失败),该函数返回False。在“将你基于16位Office的解决方案移植到32位Office ”一文中给出的Engine32函数被设计成可说明它们在每个Office 产品中的差异的示例。下面给出的是经修改后的函数,它具有更好的性能并能在更多的版本上运行。
利用在第一个函数中初始化静态变量可改善性能。随后的所有调用使用这些静态变量,而不是重复进行额外的函数调用。改善性能的另一方法是初始化一个全局或公有变量;然而,它存在一个缺陷,在一些产品中发生Reset(重置)时,在全局变量得到重新初始化前,随后的所有API调用可能失败。
Microsoft Excel 5或更高版本和Project 4或更高版本
如果Microsoft Excel 和Microsoft Project 是32位的产品,它们中的Application.OperatingSystem属性通常包含32。由于Microsoft Excel 5.0既有16位产品又有32位产品,因此产品版本号是不充分的。
Function Engine32%()
Static sEngine32%,SEval% 注释:Statics用于性能的改善。
If SEval% Then Engine32%=sEngine32%: Exit Function
If instr(Application.OperatingSystem,"32") then sEngine32%=True
Seval%=True
Engine32%=sEngine32%
End Function
Microsoft Access 1.1或更高版
Microsoft Access不具有既有16位又有32位产品的版本。通过调用SysCmd检查版本号,确定正在使用的Microsoft Access的版本。Microsoft Access 1.1在构造时没有加入一个版本号常量,因此,我们通常使用7,以确保代码可在Microsoft Access中正常工作。这种方法可用来查看你的解决方案代码进行的是一个16位还是一个32位API调用。
Function Engine32% ()
Static sEngine32%,SEval%
If SEval% Then Engine32%=sEngine32%: Exit Function
If SysCmd(7) > 2 Then sEngine32% = True
Seval%=True
End Function
Word for Wndows 2.0或更高版
由于Word不支持静态变量,Word 必须每次对Engine32函数赋值。首先,我们检查产品版本号是否表明其是一个32位版本,然后我们检查操作系统的版本,查看它是否是一个32位的操作系统。这两个步骤是需要的,因为Word 6.0以前的版本不能使用GetSystemInfo,并且Word 6.0既具有16位版又具有32位版。
Function Engine32
Engine32 = 0
If Val(AppInfo$(2)) > 5 Then
OS$ = GetSystemInfo$(23)
If Val(OS$) > 6.3 Or Len(OS$) = 0 Then Engine32 = - 1
End If
End Function
Visual Basic
虽然Visual Basic 不使用解决方案代码,但Basic代码通常要与包含上述的产品之间进行交换。Visual Basic 4.0没有 Application.OperatingSystem 属性(它不是VBA所专有的一部分),而是使用条件编译#IF和 #ELSE。如果你打算将你的代码与其他Microsoft 产品共享,你应创建下列函数(不要在别处使用条件编译):
Function Engine32%()
注释:这仅针对 VB4。
#IF WIN16
Engine32% = False
#ELSE
Engine32% = True
#ENDIF
End Function
对于更早版本的Visual Basic ,使用下列函数:
Function Engine32%()
注释:这是用于 VB1 - VB3的; 不支持#IF。
Engine32% = False
End Function
Declare方法解决方案的示例
下列代码演示了Declare方法解决方案(不包括Word ):
Declare Function GetTickCount32 Lib "Kernel32" Alias "GetTickCount" () As Long
Declare Function GetTickCount16 Lib "USER" Alias "GetTickCount" () As Long
Function GetTickCount() As Long
If Engine32() Then
GetTickCount = GetTickCount32()
Else
GetTickCount = GetTickCount16()
End If
End Function
Engine32函数可用来确定进行的API调用。Declare语句指示实际的API函数名的Alias(别名),以避免偶尔的大小写变化(32位API调用是区分大小写的),然后在函数后添加16或32以表明API函数的位数(是16位还是32位)。
这个代码可在除Word 外的所有Office 产品中复制和粘贴。WordBasic在其他Office 产品使用Basic之前已经存在,并与之不同。我们将在后面探讨Word 解决方案。
Declare方法解决方案的步骤
如果你正将解决方案转换为能在16位和32位产品上运行的解决方案,我建议你采用下列步骤:
创建一个名为APICalls的新模块;
在APICalls宏中创建Engine32函数;
定位解决方案中的所有Declare函数,并将它们移到APICalls中;
使用“Declare方法解决方案的示例”中的宏代码作为一个模板,为每个API创建函数;
自变量应与Windows 3.1 版API调用匹配;
结果应是Win32 API调用的结果(若需要,Visual Basic 将自动转换为Windows 3.1版);
添加16位API Declare行,在函数名后加上16;
添加32位API Declare行,在函数名后加上32;
进行任何数据处理;
添加一个IF行以调用相应的API;
RETURE返回变量(如果有);
测试函数。
这个过程使得在其他模块中现有的调用保持原样。一旦开发者定义并测试了APICalls模块,她或他可以将这个模块加入到其他解决方案中并可重复使用,这样可节省开发时间。这个模块的内容独立于解决方案,并且开发者可以重复使用这个模块。模块APICalls成为将来开发VBA中16位/32位解决方案的一个组成部分。
API包装的替代解决方案
我通常在API调用上加一层包装,而不是将API调用暴露在代码中。例如,我会将代码包装在GetProfileString上,以创建一个名为vbGetWinIni的函数,该函数采用相同的自变量,但返回的是字符串。我在“创建有用的内在Visual Basic和Microsoft Access函数”一文中论述了公用API包装到DLL的转换问题。
如果你以这种方式编写代码,你可能想要修改API包装函数以调用相应的API,而不是额外创建函数。
Word Declare方法解决方案示例
Word具有不同的Declare格式和句法。Word解决方案比较复杂,这是因为你不能将16位和32位Declare语句都放在相同的宏中。
解决方案将创建三个宏库:APICALL16、APICALL32(它们包含针对每个操作环境的Declare语句)以及一个具有16位/32位互操作性的宏APICALLS。这可能听起来很混乱,那么让我们一步一步讨论。
首先,我们创建一个名为APICALL16的宏库。这个宏包含所有16位API Decalre语句。
注释:这是APICALL16 -- 所有16位Declare语句都包含在此处。
Declare Function GetTickCount16 Lib "USER" Alias "GetTickCount"() As Long
Function GetTickCount
GetTickCount = GetTickCount16
End Function
其次,我们创建一个名为APICALL32的宏库。这个宏包含所有32位API Declare语句。
注释:这是 APICALL32 -- 所有32位 Declare 语句都包含在此处。
Declare Function GetTickCount32 Lib "KERNEL32"() Alias "GetTickCount" As Long
Function GetTickCount
GetTickCount = GetTickCount32
End Function
第三步,我们创建一个名为APICALLS的宏库。这个宏包含Engine32和你的解决方案代码所要调用的过程。
注释:这是APICALLS -- 这个宏中不包含任何Declare语句。
Function Engine32
Engine32 = 0
If Val(AppInfo$(2)) > 5 Then
OS$ = GetSystemInfo$(23)
If Val(OS$) > 6.3 Or Len(OS$) = 0 Then Engine32 = - 1
End If
End Function
Function GetTickCount
If Engine32 Then
GetTickCount = APICall32.GetTickCount
Else
GetTickCount = APICall16.GetTickCount
End If
End Function
注释:其他API函数调用包含在此处。
你现在可以从你的解决方案代码中调用这个函数。你必须以APICALLS开始你的调用,例如:
Sub MAIN
MsgBox Str$(APICalls.GetTickCount)
End Sub
Word Declare方法解决方案的步骤
如果你正将Word解决方案转换为能在16位和32位产品上运行的解决方案,我建议你采用下列步骤:
创建一个名为APICALLS的新模块;
在APICALLS中创建Engine32函数;
创建一个名为APICALL16的模块;
定位解决方案中的所有16位Declare语句,并将它们移到APICALL16中;
创建一个名为APICALL32的新模块;
创建相当的32位Declare语句,并将它们置于APICALL32中。
使用上述模板,为三个宏库中的每个API创建函数;
在调用所有API前,在你的解决方案代码中添加APICALLS;
测试每个函数。
这个过程使得在其他模块中现有的调用保持原样。一旦开发者定义和测试了这些宏,她或他可将这些宏添加到NORMAL.DOTMO模板中,并可在其他解决方案中重复使用这些宏,以节省时间。
类型库方法
进行API调用的类型库方法对于大多数开发者来说都是较新的内容。在Bruce McKinney的一本将由Microsoft出版的书《Visual Basic核心》中,包含了16位API调用(WIN16.TLB)的Windows API Functions函数类型库以及一个与32位API调用(WIN32.TLB)匹配的类型库。一旦这些类型库被注册,对于16位或32位版本的Office产品,相应的类型库将被装载。
一个类型库可提供Microsoft Excel 5.0或更高版、Microsoft Project 4.0或更高版、Visual Basic 4.0或更高版以及Microsoft Access 95或更高版产品的API调用的简易途径。所有这些Windows API调用变成了内在函数。(参阅我的“创建有用的内在Visual Basic和Microsoft Access函数”一文中的相关材料,它们仅适用于早期版本的Visual Basic和Microsoft Access)。由于对于我的读者来说,类型库方法是一种新的方法,我会详细逐步进行分析。
类型库的注册
下列步骤可完成在你注册中添加一个调用Windows API函数的类型库。这样,该类型库对于所有使用VBA的产品都可用,而不仅仅限于你进行该函数注册的产品。
打开任何VBA产品(例如,Microsoft Excel 5.0)。
为了创建一个模块,从“插入”菜单中选择“宏模块”。
从“工具”中选择“引用”。“引用”对话框的显示如图1。
图1:“引用”对话框
假设你未注册Windows API函数类型库,单击“浏览”按钮并定位到Win32.TLB,然后单击“确定”。对于WIN16.TLB,重复进行这个步骤。这样WIN16.TLB和WIN32.TLB都被注册。参见图2。
图2:选择类型库
在“浏览器”对话框关闭后,滚动到“引用”对话框的底部,你可以在列表中看到Windows API函数类型库(图3)。
图3:Windows API 函数的注册
关闭“引用”对话框。
从“视图”菜单中选择“对象浏览器”。
在“对象浏览”对话框上,在库/工作表下拉列表框中选择Win(Windows API 函数类型库)。所有可用的函数将显示在对象/模块和方法/属性列表框中(如图4)。
图4:Microsoft Excel 5.0“对象浏览器”显示Bruce McKinney的Windows API函数类型库中可用的对象和方法
在对象/模块列表框中,选择“Kernel”;然后在方法/属性列表框中选择“GetTickCount”。
单击“粘贴”按钮,GetTickCount出现在模块中。
下面示例列出的代码可实现在消息框中显示GetTickCount API调用的返回值。
Sub Demo()
MsgBox Str$(GetTickCount)
End Sub
不需要REGISTER命令和Declare语句。上面的代码就是你所需要的全部代码。
类型库方法解决方案的步骤
如果你正将解决方案转换为能在16位和32位产品上运行的解决方案,我建议你采用下列步骤--假设Windows API函数类型库已注册:
假设你仅使用标准的16位API调用,删除你的所有Declare语句;
检查“引用”对话框中的Wondows API函数检查框。
这就是全部步骤,你已经完成了移植。
类型库问题
类型库是一项发展中的技术,商业上可用的类型库还非常少。Bruce先生在使用Windows 3.1 API调用名设计16位和32位产品的类型库方面,作出了优异的工作。尽管如此,类型库仍存在一些问题留待解决。
Windows API函数类型库在VBA中添加了超过1000个的新保留词。所有包含在类型库中API调用变成了语言的保留词。如果你已经有一个名为ordShell的函数,你必须更改它的名称,这样才不会发生与类型库中定义的ordShell函数的冲突。
Windows API函数类型库不包括需要用户定义的类型(UDT)的API调用。这可能在下一版本中得到改善。
Windows API函数类型库方法在Microsoft Excel的speadsheet(电子数据表)中是不可用的。
这种技术是非常有希望的,并将在开发Office解决方案中简化API调用的使用。
……