韓建彬
(武警指揮學院,天津 300250)
隨著硬件的更新和軟件的升級,Windows已經進入了64位時代,與之相適應,自Office2010開始,也提供了64位的版本,并將其內置的VBA升級到了7.0。這雖然增強了對64位數據的支持,為應用程序性能的提升創(chuàng)造了條件,但同時也帶來代碼的兼容性問題。筆者曾在Excel 2003中編寫過一個從題庫中隨機選題的VBA程序[1],在32位版本Office 2010中可以正常運行,但在64位版本中,會提示“編譯錯誤:“若要在64位系統(tǒng)上使用,則必須更新此項目中的代碼。請檢查并更新Declare語句,然后用PtrSafe屬性標記它們?!背霈F(xiàn)這樣的情況要從64位系統(tǒng)的特性和VBA的改變來分析。
對于Windows來說,64位系統(tǒng)理論上能夠使用更多的指令集,一次可以處理8個字節(jié)的數據,并擁有更大的尋址空間,從而突破了32位系統(tǒng)中4GB物理內存和每個進程2GB虛擬內存的限制[2],有利于充分發(fā)揮硬件的潛能。而在程序設計中,64位系統(tǒng)帶來的最明顯改變就是指針和用于標識窗口、設備、文件等對象的句柄都變成了64位,即8個字節(jié)的長度。如果使用Microsoft Spy++(X64版)對64位系統(tǒng)進行檢查,就能直觀地看到這一變化[3]。
具體到VBA7.0,可以同時支持32位和64位的應用,并且在32位版本的Office中,以前所編寫的VBA代碼無需作任何修改,完全可以正常運行。而在64位版本的Office中,VBA為了與系統(tǒng)的變化相協(xié)調,特別是針對指針、句柄及其他64位數據,增加了相應的關鍵字和數據類型[4]。如果在64位的VBA環(huán)境中,沒有正確使用相應的關鍵字和數據類型,就會引發(fā)編譯錯誤或不可預知的運行結果。
如果VBA代碼中包含對Windows API的聲明語名,在以前版本的VBA環(huán)境中,其聲明語句的格式通常為Public/Private Declare Function FunctionName Lib"Libname"alias"aliasname"(argument list)As Type,但這僅適用于32的環(huán)境。在64位的VBA環(huán)境中,必須在Declare前面添加上PtrSafe關鍵字,以表明代碼適用于64位環(huán)境,否則將不能進行編譯。前面出現(xiàn)的編譯錯誤提示,首先就是因為缺少了這一關鍵字。
僅在Windows API的聲明語句中添加PtrSafe關鍵字,常常并不能徹底消除編譯錯誤,因為還涉及到數據類型的匹配問題。如果在API的聲明和調用中使用了句柄或指針,則必須修改聲明中的參數類型。
在VBA 7.0之前版本中,沒有針對指針和句柄的特定數據類型,通常使用Long數據類型(長度為4個字節(jié))來定義指針和句柄。而在64位的環(huán)境中,指針和句柄變?yōu)?字節(jié),不能直接轉換為Long數據類型。為了解決這一問題,VBA 7.0中包含了真正的指針數據類型LongPtr,它在32位的Office 2010中解釋為4字節(jié)的數據類型,在64位版本中則解釋為8字節(jié)數據類型,因此,需要將聲明語句中涉及句柄和指針的變量由Long類型修改為LongPtr類型。
這是在64位的Office 2010及以后的Office版本中所使用的8個字節(jié)的數據類型,用于定義64位整數。64位環(huán)境中的某些函數需要使用這一特定的數據類型作為參數或返回值的類型。LongLong與較短的整數類型之間不允許進行隱式轉換,必須通過轉換函數才能將LongLong(包括64位環(huán)境中的LongPtr)顯式賦予32位的整數類型。
由上面的分析可知,要想讓VBA代碼能夠運行于64位版本的VBA 7.0環(huán)境中,必須更新其中有關Windows API應用程序編程接口的聲明。首先,要為所有的Declare語句添加PtrSafe關鍵字。其次,還需要修改這些Declare語句內所有句柄和指針的定義,以使用新的64位兼容的LongPtr類型。第三,需要使用新的LongLong數據類型保存64位的整數,包括用戶自定義類型中所含有的指針、句柄以及64位整數,使其真正符合系統(tǒng)對數據類型的要求。第四,在函數調用過程中,必須驗證所有變量的賦值是否正確,以防因類型不匹配而引發(fā)編譯或運行錯誤。
在VBA 6.0環(huán)境下,聲明Windows API函數的具體語句包含在微軟提供的Win32API.TXT文件中,而在VBA 7.0出現(xiàn)以后,微軟又發(fā)布了Win32API_PtrSafe.TXT文件,提供了有關函數在VBA 7.0中的聲明語句。
例如,在Office 2003中,借助SetTimer函數實現(xiàn)隨機選擇題目的程序,使用了如下的聲明語句:
而在Office 2010的64位版本中,則需要改寫成如下格式:
從兩者的對比可以看出,在Function前面添加了“PtrSafe”關鍵字,并且其中的窗口句柄、定時器ID、回調函數指針都換成了LongPtr類型,這樣才能在64位環(huán)境中運行。
再如,用于引用程序主窗口句柄的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程序的運行環(huán)境,以便按照具體的環(huán)境編譯相應的語句,從而保證代碼的兼容性。
在MSDN中,常量Win64被解釋為“用于測試代碼是以32位還是64位形式運行的”,這樣的表述并不是很明確,其中的“代碼”具體指運行環(huán)境中的哪一個層次,是操作系統(tǒng)還是Office軟件?通過在不同的運行環(huán)境中進行實驗,其結果如下:在64位的Windows系統(tǒng)中,使用Office 2010及2013進行測試,如果是32位的版本,“Win64”為False,“VBA7”為True;如果是64位的版本,“Win64”為True,“VBA7”為True。由此可見,“Win64”主要表示當前的Office是否為64位,而“VBA7”則表示當前的VBA環(huán)境是否為VBA7.0。
根據MSDN中的說明,VBA 7.0可以同時支持32位和64位的程序代碼,且32位Office中的VBA7.0與以前的VBA完全兼容,只有64位版本Office中的VBA7.0才會產生代碼不兼容的問題。因此,可以使用如下的條件編譯語句:
例如,為了存儲FindWindow函數的返回值,聲明變量hWnd,其條件編譯代碼為:
這樣就能保證變量聲明在32位和64位環(huán)境中的兼容性。
而對于某些在64位和32位的VBA 7.0環(huán)境中也具有不同聲明要求的函數,其條件編譯要使用嵌套結構,需同時使用Vba7和Win64作為編譯條件。如WindowFromPoint函數,由于在不同的環(huán)境中參數的類型不同,需要使用如下的編譯條件。
雖然以上的結構還可以簡化,但為了使代碼中的邏輯關系更為清晰,仍建議采用這樣的形式。
對于Office來說,32位與64位版本共存的狀況必然還要持續(xù)一定的時間,VBA也仍將是Office開發(fā)中的重要工具,為了提高VBA代碼的兼容性,必需根據運行環(huán)境的發(fā)展變化,按照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.