自動化程序的概念
“自動化程序”指的是通過電腦編程來代替人類手工操作的一類程序或軟件。這類程序具有智能性高、應用范圍廣的優點,但是自動化程序的開發難度大、所用技術雜。
本文對自動化程序開發的各個方面進行講解。
-
常見的處理對象
自動化程序要處理的對象,與具體的業務需求有關。假設制作一個QQ信息群發工具,所處理對象就是QQ這個軟件;如果要制作游戲外掛,處理對象就是那個游戲的界面。
常見的處理對象是與電腦有關的一切內容。包括:文件和數據、Office組件、服務和進程、注冊表信息、網頁、遠程計算機和服務器、AD用戶信息,以及電腦上打開的各種窗口和對話框等界面元素。
-
主要的實現方法
通過COM接口:
自動化程序的概念非常寬泛,很多自動化處理都可以通過COM接口來實現。例如注冊表的查看和修改,使用手工操作的話必須打開注冊表編輯器,然后用鼠標點擊開要處理的那個鍵值,通過界面一步一步操作才能完成。而通過COM接口則無需打開編輯器,程序中只需要傳遞要修改的注冊表路徑即可。
通過窗口界面:
另一類則需要把窗口界面作為自動化處理的對象,例如要在一個第三方軟件中執行有關操作,或者在網頁上進行表單的填寫和提交等。通過窗口界面實現的自動化主要分為瀏覽器和網頁、一般窗口界面這兩大類型。
-
自動化程序的共性
面向的處理對象經常是樹狀結構
Windows系統中很多對象都呈現為樹形結構。例如文件資源管理器、注冊表、一個窗口中的所有句柄形成的句柄數、XML文檔、HTML文檔、JSON數據等,這些都難以用Excel表格之類的列表結構來描述。實際上,世界各國的行政划分也呈現為樹形結構,省份、城市、鄉鎮等具有明顯的上下級關系。
先進行目標的查找和定位,后執行有關操作
自動化程序中最常用的技術是目標的查找和定位。例如要點擊網頁中的某個按鈕,首先要用Get或Find之類的方法定位到這個按鈕,接下來獲取或設置它的屬性,或者執行它的方法。
樹形結構看起來很復雜,但是不外乎父子、兄弟這兩類關系。使用恰當的篩選條件就可以准確定位到想要訪問的對象。
自動化程序必須依賴環境才能正常運行
一個自動化程序經常需要訪問多個軟件、窗口,如果把自動化程序復制到另一台電腦,那台電腦中必須安裝有相應的軟件才能正常使用。
自動化程序的常用技術和編程語言
能夠用作自動化程序開發的語言非常多。不過Windows系統中常用的編程語言有:
非托管語言:VBA/VB6/VBS
基於.NET:C#/VB.NET/Powershell
Python
下面表格列出的是最常用的處理對象,以及在各個編程語言中所使用的模塊名稱。
瀏覽器和網頁相關的自動化
如今的辦公方式,與瀏覽器、網頁相關的操作特別多。例如自動向網站申請信息,或者從其他網站抓取有用的數據,都是通過網絡和服務器實現的。
各種編程語言中的實現方式如表所示。
-
InternetExplorer
IE是Windows系統自帶的瀏覽器,在程序中可以通過創建COM對象的方式啟動瀏覽器,並且操作網頁中的元素。
例如使用IE瀏覽器打開網頁,在4個文本框中輸入數字,把計算結果返回給VBA。
通過以下程序,向網頁自動填寫了4個數字,並且把得到的結果打印到了立即窗口。
Sub Test24() Dim IE As InternetExplorer Dim HDoc As HTMLDocument Set IE = New InternetExplorer With IE .Visible = True .navigate "http://www.atoolbox.net/Tool.php?Id=950" Set HDoc = .document End With HDoc.getElementById("a1").Value = 3 HDoc.getElementById("a2").Value = 3 HDoc.getElementById("a4").Value = 7 HDoc.getElementById("a8").Value = 7 HDoc.getElementById("btn-generate").Click Debug.Print HDoc.getElementById("txt-result").innerText IE.Quit End Sub
-
Selenium
Selenium是一種自動化測試工具,可以操作Chrome、Firefox等多種瀏覽器,可以在各種語言中使用Selenium。
下面的程序,啟動Chrome瀏覽器,打開網頁,執行JavaScript函數直接算出結果。
WD = New Chrome.ChromeDriver(chromeDriverDirectory:="D:\Selenium") WD.Navigate.GoToUrl("http://www.atoolbox.net/Tool.php?Id=950") WD.FindElementById("a1").Clear() WD.FindElementById("a2").Clear() WD.FindElementById("a4").Clear() WD.FindElementById("a8").Clear() WD.FindElementById("a1").SendKeys("3") WD.FindElementById("a2").SendKeys("3") WD.FindElementById("a4").SendKeys("7") WD.FindElementById("a8").SendKeys("7") WD.FindElementById("btn-generate").Click() Debug.WriteLine(WD.FindElementById("txt-result").Text) Me.TextBox1.Text = WD.ExecuteScript("return solve24(arguments[0],arguments[1],arguments[2],arguments[3])", 5, 6, 7, 8) 'WD.Quit()
-
XMLHttp
Sub GetWeather() Dim X As XMLHTTP60 Dim Doc As DOMDocument60 Dim citys As IXMLDOMNodeList Dim city As IXMLDOMElement Const url = "http://flash.weather.com.cn/wmaps/xml/beijing.xml" Set X = New XMLHTTP60 With X .Open "GET", url, False .send 'Debug.Print .responseText Set Doc = .responseXML End With Set citys = Doc.SelectNodes("beijing/city") Debug.Print citys.Length For Each city In citys Debug.Print city.getAttribute("cityname"), city.getAttribute("tem1") Next city End Sub
XMLHttp/WinHttp不需要瀏覽器和網頁,可以向目標URL發送Get或Post請求,然后把返回的信息進行解析,就達到了網絡信息獲取的目的。
上面的程序,返回如下一個完整的XML文檔
<beijing dn="day"> <city cityX="232" cityY="190.8" cityname="延慶" centername="延慶" fontColor="FFFFFF" pyName="yanqing" state1="0" state2="0" stateDetailed="晴" tem1="21" tem2="3" temNow="21" windState="西風微風級" windDir="西南風" windPower="2級" humidity="18%" time="15:00" url="101010800"/> <city cityX="394" cityY="157" cityname="密雲" centername="密雲" fontColor="FFFFFF" pyName="miyun" state1="0" state2="0" stateDetailed="晴" tem1="21" tem2="4" temNow="22" windState="北風微風級轉3-4級" windDir="西風" windPower="1級" humidity="26%" time="15:00" url="101011300"/> <city cityX="332" cityY="142" cityname="懷柔" centername="懷柔" fontColor="FFFFFF" pyName="huairou" state1="0" state2="0" stateDetailed="晴" tem1="22" tem2="5" temNow="20" windState="西北風微風級" windDir="西南風" windPower="1級" humidity="30%" time="15:00" url="101010500"/> <city cityX="261" cityY="248" cityname="昌平" centername="昌平" fontColor="FFFFFF" pyName="changping" state1="0" state2="0" stateDetailed="晴" tem1="23" tem2="6" temNow="23" windState="西北風轉北風微風級" windDir="南風" windPower="2級" humidity="28%" time="15:00" url="101010700"/> <city cityX="439" cityY="232" cityname="平谷" centername="平谷" fontColor="FFFFFF" pyName="pinggu" state1="0" state2="0" stateDetailed="晴" tem1="22" tem2="2" temNow="22" windState="北風微風級轉3-4級" windDir="西南風" windPower="3級" humidity="30%" time="15:00" url="101011500"/> <city cityX="360" cityY="265" cityname="順義" centername="順義" fontColor="FFFFFF" pyName="shunyi" state1="0" state2="0" stateDetailed="晴" tem1="22" tem2="6" temNow="22" windState="西北風微風級轉3-4級" windDir="東南風" windPower="1級" humidity="31%" time="15:00" url="101010400"/> <city cityX="167" cityY="317.65" cityname="門頭溝" centername="門頭溝" fontColor="FFFFFF" pyName="mentougou" state1="0" state2="0" stateDetailed="晴" tem1="24" tem2="6" temNow="21" windState="西北風微風級轉北風3-4級" windDir="南風" windPower="2級" humidity="32%" time="15:00" url="101011400"/> <city cityX="264.5" cityY="300.3" cityname="海淀" centername="海淀" fontColor="FFFFFF" pyName="haidian" state1="0" state2="0" stateDetailed="晴" tem1="23" tem2="7" temNow="21" windState="北風微風級轉3-4級" windDir="南風" windPower="1級" humidity="33%" time="15:00" url="101010200"/> <city cityX="344.3" cityY="317.65" cityname="朝陽" centername="朝陽" fontColor="FFFFFF" pyName="chaoyang" state1="0" state2="0" stateDetailed="晴" tem1="23" tem2="8" temNow="22" windState="北風3-4級轉4-5級" windDir="西南風" windPower="2級" humidity="32%" time="15:00" url="101010300"/> <city cityX="255" cityY="341.5" cityname="石景山" centername="石景山" fontColor="FFFFFF" pyName="shijingshan" state1="0" state2="0" stateDetailed="晴" tem1="23" tem2="7" temNow="21" windState="北風微風級轉3-4級" windDir="南風" windPower="2級" humidity="34%" time="15:00" url="101011000"/> <city cityX="310.05" cityY="339.3" cityname="市中心" centername="市中心" fontColor="FFFF00" pyName="shizhongxin" state1="0" state2="0" stateDetailed="晴" tem1="22" tem2="7" temNow="21" windState="北風3-4級轉4-5級" windDir="西南風" windPower="2級" humidity="33%" time="15:00" url="101010100"/> <city cityX="282.95" cityY="361" cityname="豐台" centername="豐台" fontColor="FFFFFF" pyName="fengtai" state1="0" state2="0" stateDetailed="晴" tem1="23" tem2="7" temNow="21" windState="北風微風級轉3-4級" windDir="西南風" windPower="1級" humidity="32%" time="15:00" url="101010900"/> <city cityX="198" cityY="386" cityname="房山" centername="房山" fontColor="FFFFFF" pyName="fangshan" state1="0" state2="0" stateDetailed="晴" tem1="22" tem2="7" temNow="21" windState="北風微風級轉3-4級" windDir="南風" windPower="2級" humidity="34%" time="15:00" url="101011200"/> <city cityX="319" cityY="400" cityname="大興" centername="大興" fontColor="FFFFFF" pyName="daxing" state1="0" state2="0" stateDetailed="晴" tem1="22" tem2="7" temNow="21" windState="北風3-4級轉4-5級" windDir="南風" windPower="2級" humidity="33%" time="15:00" url="101011100"/> <city cityX="374" cityY="355" cityname="通州" centername="通州" fontColor="FFFFFF" pyName="tongzhou" state1="0" state2="0" stateDetailed="晴" tem1="22" tem2="5" temNow="22" windState="西北風3-4級" windDir="西南風" windPower="2級" humidity="30%" time="15:00" url="101010600"/> </beijing>
VBA程序的最后,遍歷每個city的氣溫,結果:
一般窗口界面的自動化
“一般窗口”指的是Windows系統桌面上彈出的各種應用軟件的窗口、對話框等。
-
Windows API 函數
Windows API函數包含幾十個類別的函數,涵蓋領域包括窗口和句柄、注冊表和文件讀寫、進程和線程等。自動化程序開發過程中一般都有相應的對象模型,例如VBA中使用FSO可以方便地處理文件系統,而無需使用API函數。
訪問第三方軟件的窗口時,使用API函數中的FindWindow、FindWindowEx可以查找窗口及其里面包含的控件的句柄,得到句柄以后可以進一步獲取其他相關控件的句柄,或者使用PostMessage、SendMessage函數向句柄發送消息。
假設瀏覽器上彈出一個另存為對話框,那么如何修改默認的路徑、文件名稱,以及如何自動點擊“保存”按鈕呢?
首先在Spy工具中查看句柄數
然后聲明有關API函數和常量,定位到文本框和按鈕,發送消息。
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr Private Declare PtrSafe Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As LongPtr, ByVal hWnd2 As LongPtr, ByVal lpsz1 As String, ByVal lpsz2 As String) As LongPtr Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr Private Const BM_CLICK = &HF5 Private Const WM_SETTEXT = &HC Sub ClickSave() Dim h(10) As Long h(0) = FindWindow("#32770", "另存為") h(1) = FindWindowEx(h(0), 0, "DUIViewWndClassName", vbNullString) h(2) = FindWindowEx(h(1), 0, "DirectUIHWND", vbNullString) h(3) = FindWindowEx(h(2), 0, "FloatNotifySink", vbNullString) h(4) = FindWindowEx(h(3), 0, "ComboBox", vbNullString) h(5) = FindWindowEx(h(4), 0, "Edit", vbNullString) h(6) = FindWindowEx(h(0), 0, "Button", "保存(&S)") SendMessage h(5), WM_SETTEXT, 0, ByVal "Test.htm" SendMessage h(6), BM_CLICK, 0, 0 End Sub
-
MSAA技術
MSAA的全稱是Microsoft Active Accessibility,是一種基於COM的用於提高訪問Windows應用程序的性能。UI程序可以暴露出一個Interface,以便於另一個程序對其進行控制訪問。
該技術中唯一的對象是IAccessible,通過該對象可以獲知窗口或控件的屬性,執行默認的方法。
使用AccessibleChildren函數遍歷子對象。
下面的例子演示了如何定位和點擊記事本窗口的最大化按鈕。
Sub 遍歷子級IAccessible() Dim A1 As IAccessible Dim hNotepad As Long Dim Result As Long hNotepad = FindWindow("Notepad", vbNullString) Result = AccessibleObjectFromWindow1(hNotepad, OBJID_WINDOW, MyUUID, A1) Dim Child As Accessibility.IAccessible Dim ChildCount As Long Dim Children() As Variant Dim Got As Long Dim i As Long ChildCount = A1.accChildCount If ChildCount > 0 Then ReDim Children(ChildCount - 1) Call AccessibleChildren(A1, 0, ChildCount, Children(0), Got) For i = 0 To Got - 1 If TypeOf Children(i) Is Accessibility.IAccessible Then Set Child = Children(i) Debug.Print i, Child.accName(CHILDID_SELF) ElseIf VarType(Children(i)) = VBA.VbVarType.vbLong Then Debug.Print i, A1.accName(Children(i)) End If Next i Set Child = Children(1) '得到 1 none 標題欄 ChildCount = Child.accChildCount ReDim Children(ChildCount - 1) Call AccessibleChildren(Child, 0, ChildCount, Children(0), Got) For i = 0 To Child.accChildCount - 1 Debug.Print i, Child.accName(Children(i)) Next i Call Child.accDoDefaultAction(Children(2)) '執行最大化 End If End Sub
-
UI Automation技術
UI Automation(簡稱UIA)是Microsoft .NET框架下提供的一種用於自動化測試的技術,UIA提供對桌面上大多數用戶界面元素的編程訪問,從而可以讓終端用戶利用程序操作界面,而不是手工接觸界面。
UI Automation技術以桌面為根元素,在桌面上打開的所有窗口和對話框都是桌面的子元素。假設桌面上打開了Excel、記事本、運行對話框,自動化樹的示意圖:
UI Automation把窗口和控件都作為AutomationElement來對待,通過FindFirst、FindAll方法或者TreeWalker,結合屬性條件來定位其他元素。
每一個AutomationElement都支持一些模式(Pattern)。所謂模式,指的是控件的某方面的行為,例如WIndowPattern表示窗口模式,通過該模式可以獲取和設置窗口的狀態。
如果要自動往運行對話框中輸入命令,並且自動點擊“確定”按鈕。
首先要在Inspect中查看一下該窗口的自動化樹結構。
根據以上信息就可以寫代碼了
Public UIA As UIAutomationClient.CUIAutomation Sub 操作運行對話框() Dim AE1 As UIAutomationClient.IUIAutomationElement Dim AE2 As UIAutomationClient.IUIAutomationElement Dim AE3 As UIAutomationClient.IUIAutomationElement Dim PC1 As UIAutomationClient.IUIAutomationPropertyCondition Dim PC2 As UIAutomationClient.IUIAutomationPropertyCondition Dim PC3 As UIAutomationClient.IUIAutomationPropertyCondition Set UIA = New UIAutomationClient.CUIAutomation Set PC1 = UIA.CreatePropertyCondition(propertyId:=UIAutomationClient.UIA_PropertyIds.UIA_NamePropertyId, Value:="運行") Set AE1 = UIA.GetRootElement.FindFirst(scope:=TreeScope_Children, Condition:=PC1) Debug.Print AE1.CurrentName Set PC2 = UIA.CreatePropertyCondition(propertyId:=UIAutomationClient.UIA_PropertyIds.UIA_ControlTypePropertyId, Value:=UIAutomationClient.UIA_ControlTypeIds.UIA_ComboBoxControlTypeId) Set AE2 = AE1.FindFirst(scope:=TreeScope_Children, Condition:=PC2) Debug.Print AE2.CurrentName Dim VP As UIAutomationClient.IUIAutomationValuePattern Set VP = AE2.GetCurrentPattern(PatternId:=UIAutomationClient.UIA_PatternIds.UIA_ValuePatternId) VP.SetValue "regedit.exe" Set PC3 = UIA.CreatePropertyCondition(propertyId:=UIAutomationClient.UIA_PropertyIds.UIA_NamePropertyId, Value:="確定") Set AE3 = AE1.FindFirst(scope:=TreeScope_Children, Condition:=PC3) Dim IP As UIAutomationClient.IUIAutomationInvokePattern Set IP = AE3.GetCurrentPattern(PatternId:=UIAutomationClient.UIA_PatternIds.UIA_InvokePatternId) IP.Invoke End Sub
運行上述程序,可以看到自動輸入了regedit.exe,並且自動打開了注冊表編輯器。
使用UI Automation技術可以針對沒有句柄的控件實現自動化。
-
鼠標和鍵盤的自動化
自動化程序開發的過程中,經常需要在一個控件中點擊鼠標或按下鍵盤,如果這個控件具有句柄,最好的方式是利用PostMessage/SendMessage向該句柄發送消息。如果沒有句柄,可以先激活該控件使之具有焦點,然后利用API函數mouse_event、keybd_event直接操作硬件。還可以使用SendKeys方法發送按鍵、組合鍵。
以上3個方法都不是以句柄為中心,因此只作用於當前活動控件,可靠性較差。
另外,SendKeys這個術語同名不同源,在很多編程環境中都能看到它。常見的有:
- Excel.Application.SendKeys
- VBA.Interaction.SendKeys
- IWshRuntimeLibrary.WshShell.SendKeys
- System.Windows.Forms.SendKeys(C#/VB.NET)
- OpenQA.Selenium.IWebElement.SendKeys(Selenium)
Office多組件編程
Excel、PowerPoint、Outlook、Word等都有相應的VBA對象模型。多個組件之間的協同編程非常普遍。
-
應用程序對象的取得
要在其他語言的程序代碼中訪問Office組件,首先要獲取到該組件的Application對象。
前期綁定指的是事先向當前工程中添加了Office組件的引用,后期綁定則不添加引用。
.NET語言中利用反射中的CreateInstance可以根據注冊表中指定的ProgID創建各種對象,例如C#或VB.NET可以用如下代碼創建Excel:
Dim ExcelType As Type = Type.GetTypeFromProgID(progID:="Excel.Application") Dim ExcelApp As Object = Activator.CreateInstance(type:=ExcelType) ExcelApp.Visible = True
-
被控組件類型的聲明和枚舉常量的使用
要想在編程語言中聲明Office組件的類型以及該組件中的枚舉常量,必須使用前期綁定方式。
非托管語言中引用了Office組件后,代碼中輸入組件名稱加上小數點,可以直接喚出其成員,例如Outlook.
在.NET語言中,則需要輸入Microsoft.Office.Interop.Outlook.
VB6中創建Outlook郵件代碼如下:
Sub CreateMail() Dim OutlookApp As Outlook.Application Dim Mail As Outlook.MailItem Dim R As Outlook.Recipient Set OutlookApp = New Outlook.Application Set Mail = OutlookApp.CreateItem(itemtype:=Outlook.OlItemType.olMailItem) With Mail .Subject = "冬天即將來臨" Set R = .Recipients.Add(Name:="32669315@qq.com") R.Type = Outlook.OlMailRecipientType.olTo R.Resolve .Attachments.Add "E:\accExplorer\log.txt" .Body = "詳見附件。" .Display End With End Sub
VB.NET中創建Outlook郵件
Imports Outlook = Microsoft.Office.Interop.Outlook Class Form1 Sub CreateMail() Dim OutlookApp As Outlook.Application Dim Mail As Outlook.MailItem Dim R As Outlook.Recipient OutlookApp = New Outlook.Application Mail = OutlookApp.CreateItem(ItemType:=Outlook.OlItemType.olMailItem) With Mail .Subject = "冬天即將來臨" R = .Recipients.Add(Name:="32669315@qq.com") R.Type = Outlook.OlMailRecipientType.olTo R.Resolve() .Attachments.Add("E:\accExplorer\log.txt") .Body = "詳見附件。" .Display() End With End Sub End Class
-
使用Office的事件
Office組件都支持事件編程。所謂事件編程就是當Office軟件中發生了某種變化自動觸發的一個過程。該過程可以定義在Office組件自身的VBA工程中,也可以定義在主控程序中。
例如Excel中新建一個工作簿、用鼠標選擇了一個區域,或者Outlook中發出一個郵件或會議,收到了一封郵件或會議邀請、某個郵件被刪除時都會觸發事件。
VBA/VB6作為主控程序時,需要在類模塊中使用WithEvents聲明事件變量。例如在Excel的ThisWorkbook模塊中創建Outlook的事件
Public WithEvents OutlookApp As Outlook.Application Private Sub OutlookApp_ItemSend(ByVal Item As Object, Cancel As Boolean) Debug.Print Item.Subject & "已發出" End Sub Private Sub OutlookApp_NewMailEx(ByVal EntryIDCollection As String) Dim Item As Object Set Item = OutlookApp.Session.GetItemFromID(EntryIDCollection) Debug.Print TypeName(Item) End Sub Sub Main() Set OutlookApp = GetObject(, "Outlook.Application") End Sub
C#/作為主控程序,使用+=與-=動態添加和移除事件。
using System; using System.Diagnostics; using Outlook = Microsoft.Office.Interop.Outlook; using System.Runtime.InteropServices; class Form1 { private Outlook.Application OutlookApp; private void Button1_Click(object sender, EventArgs e) { OutlookApp = Marshal.GetActiveObject("Outlook.Application"); OutlookApp.ItemSend += OutlookApp_ItemSend; } private void OutlookApp_ItemSend(object Item, ref bool Cancel) { Debug.WriteLine(Item.Subject); } private void Button2_Click(object sender, EventArgs e) { OutlookApp.ItemSend -= OutlookApp_ItemSend; } }
VB.NET使用AddHandler和RemoveHandler
Imports Outlook = Microsoft.Office.Interop.Outlook Imports System.Runtime.InteropServices Class Form1 Private OutlookApp As Outlook.Application Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click OutlookApp = Marshal.GetActiveObject("Outlook.Application") AddHandler OutlookApp.ItemSend, AddressOf OutlookApp_ItemSend End Sub Private Sub OutlookApp_ItemSend(Item As Object, ByRef Cancel As Boolean) Debug.WriteLine(Item.Subject) End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click RemoveHandler OutlookApp.ItemSend, AddressOf OutlookApp_ItemSend End Sub End Class
當點擊Button1時,Outlook發出任何一個項目,都會通知給.NET程序。當點擊Button2時,移除事件,不會收到通知。
-
運行被控組件VBA工程中的宏
Excel、PowerPoint、Word的Application對象均有Run方法,因此只要獲取到應用程序對象,就可以執行到該對象的VBA宏。
假設Excel VBA的工程中寫了一個計算兩個數字中最大值的函數:
然后在VB.NET中調用上述函數:
Imports Excel = Microsoft.Office.Interop.Excel Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim ExcelApp As Excel.Application = GetObject(, "Excel.Application") Dim M As Short = ExcelApp.Run(Macro:="老劉的工程.模塊1.Max", Arg1:=23, Arg2:=8) MsgBox("最大值是" & M) End Sub End Class
需要注意的是,如果過程或函數書寫在了Outlook VBA中,則不能通過Run方法來調用。
自動化程序的啟動方式
對於一個開發完成的自動化程序,它在何時被執行?執行頻率是如何指定的,這是必須要考慮的。通常情況下有如下3種執行方式:
- 手動啟動
根據需要由使用程序的人來執行。
- 由其他事件啟動
例如文件夾中某個文件被重命名、文件內容被修改等都可以作為自動化程序的觸發條件
- 任務計划
任務計划程序,是Windows系統自帶的功能。新建一個計划,設置要啟動的可執行文件的路徑和參數,以及執行周期。例如每次開機時自動打開QQ,或者每天上午十點自動執行指定的Python腳本。
調用技術
當使用一門編程語言無法或難以實現的自動化程序,就需要跨語言調用了,例如VBA中計算一個文件的MD5值需要編寫很多代碼,但是調用C#、PowerShell則方便地多。從方式上區分主要分為動態鏈接庫的調用和可執行文件調用這兩大類。
-
類、COM對象、動態鏈接庫的調用
這一類調用的目的,主要是為了使用其中定義的函數和方法。
假設一個VB.NET類庫的項目名稱為Example,其中包含兩個類Car和Person,每個類中包含若干函數。
Public Class Car Public Sub Go() End Sub Public Function Price() As Long End Function End Class Public Class Person Public Sub Walk() End Sub Public Function Age(birthday As Date) As Long End Function End Class
那么在VBA中調用以上項目生成的Example.dll
Private Instance As Example.Car Sub Test() Set Instance = New Example.Car 'Set Instance = CreateObject("Example.Car") Call Instance.Go() Debug.Print Instance.Price Set Instance = Nothing End Sub
其中,Example.Car是一個ProgID,存儲於注冊表中。
如果是前期綁定,使用New即可創建實例。如果是后期綁定,使用CreateObject創建新對象。
以上實例講的是自己創建類庫,然后在另一門語言中調用。
也可以直接調用現成的COM對象,例如字典、正則表達式用法都一樣。
-
exe可執行文件、另一門編程語言的調用
這一類調用的目的,主要是為了啟動另一個進程。
電腦中有非常多的擴展名為exe的可執行文件,例如控制面板、Excel、記事本、Chrome瀏覽器,這些應用程序的實質都是一個可執行文件。另外還有一類特殊的可執行文件,是用於解釋語言腳本的解釋器,例如VBS的解釋器CScript.exe,py腳本的解釋器Python,ps1腳本的解釋器PowerShell.exe等。還有一類是開發人員自己生成的文件,例如使用C#生成一個控制台應用程序文件。
在VBA中調用可執行文件有多種方式:
- Shell
- ShellExecute
- WshShell.Run和WshShell.Exec
在C#/VB.NET通過Process類啟動一個進程
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim p As Process p = New Process With p .StartInfo.FileName = "C:\Users\Administrator\AppData\Local\Programs\Python\Python36-32\Python.exe" .StartInfo.Arguments = "D:\Temp\1.py" .StartInfo.RedirectStandardOutput = True .StartInfo.UseShellExecute = False .Start() Debug.WriteLine(.StandardOutput.ReadToEnd) End With End Sub
1.py中的內容為:
在VB.NET中運行上述程序,看到立即窗口中出現了Python的運行結果。
調用可執行文件的過程中,需要了解命令行與參數、標准輸入、標准輸出、標准錯誤的重定向等知識。
常用的調用解釋型腳本語言的有:
Cmd.exe Test.bat
Cscript.exe Test.vbs
PowerShell.exe Test.ps1
Python.exe Test.py
-
同步和異步
同步調用:被調用的程序運行結束后,主程序接着向下運行
異步調用:主控程序啟動了其他進程,但是無論其他進程是否運行完,都繼續向下運行
WshShell.Run方法可以設置是否等待
一般情況下優先使用同步調用方式。但是遇到主控程序發生阻塞,又希望程序能夠自己解開阻塞,此時應使用異步調用方式。例如發郵件時彈出Outlook的安全對話框、IE瀏覽器彈出上傳或另存為對話框、Excel彈出了“加載項”對話框等。
Private WShell As IWshRuntimeLibrary.WshShell Sub 運行可執行應用程序() Dim Result As Long Set WShell = New IWshRuntimeLibrary.WshShell Result = WShell.Run(Command:="Notepad.exe E:\Memo.txt", WindowStyle:= IWshRuntimeLibrary.WshMaximizedFocus, WaitOnReturn:=True) If Result = 0 Then Debug.Print "成功執行。" End If End Sub
上述代碼,自動啟動記事本並且打開指定路徑的文件。由於最后一個參數等待為True,所以記事本不關閉的情況下,VBA會一直卡在這句代碼。因為屬於同步執行。
本文作者
劉永富
Microsoft Office 2003 Expert
Microsoft Office 2010 Master
Microsoft Outlook 2016 Specialist
VBA Expert
學習更多自動化知識,請加QQ群:
或者微信群: