摘要
這篇技術文章探討如何編寫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調用的使用。
|