Windows自动化程序开发指南

news2024/12/26 22:07:21

自动化程序的概念

“自动化程序”指的是通过电脑编程来代替人类手工操作的一类程序或软件。这类程序具有智能性高、应用范围广的优点,但是自动化程序的开发难度大、所用技术杂。

本文对自动化程序开发的各个方面进行讲解。

  • 常见的处理对象

自动化程序要处理的对象,与具体的业务需求有关。假设制作一个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会一直卡在这句代码。因为属于同步执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2107260.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

公认最好的跑步耳机分享,选购骨传导运动耳机需注意的五大陷阱!

跑步&#xff0c;不仅是一种锻炼身体的方式&#xff0c;更是一种生活态度的体现。它让我们在汗水中释放压力&#xff0c;在节奏中感受生命的律动。而音乐&#xff0c;作为跑步时的完美伴侣&#xff0c;能够激发我们的运动潜能&#xff0c;让我们的跑步之旅更加愉悦。因此&#…

软件测试自动化面试题(含答案)

1.如何把自动化测试在公司中实施并推广起来的&#xff1f; 选择长期的有稳定模块的项目 项目组调研选择自动化工具并开会演示demo案例&#xff0c;我们主要是演示selenium和robot framework两种。 搭建自动化测试框架&#xff0c;在项目中逐步开展自动化。 把该项目的自动化…

示波器基础知识汇总(2)

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 送给大学毕业后找不到奋斗方向的你&#xff08;每周不定时更新&#xff09; 中国计算机技术职业资格网 上海市工程系列计算机专…

SD-WAN解决企业远程服务难题

在当今数字化和全球化的商业环境中&#xff0c;企业不再受限于地理位置。远程工作和分布式团队已成为常态&#xff0c;但随之而来的是对网络连接的更高需求。本文将讨论企业远程服务中的挑战&#xff0c;并介绍一个解决这些挑战的有效方案——SD-WAN。 随着远程工作的增加&…

Buzzer:一款针对eBPF的安全检测与模糊测试工具

关于Buzzer Buzzer是一款功能强大的模糊测试工具链&#xff0c;该工具基于Go语言开发&#xff0c;可以帮助广大研究人员简单高效地开发针对eBPF的模糊测试策略。 功能介绍 下面给出的是当前版本的Buzzer整体架构&#xff1a; 元素解析&#xff1a; 1、ControlUnit&#xff1a…

查看元神操作系统的版本

1. 背景 本文通过元神操作系统的API调用来获取元神系统的版本&#xff0c;并显示在屏幕上。 2. 方法 &#xff08;1&#xff09;编写程序 本例先设置系统调用的参数&#xff1a;第一个参数设置为API_OS_VER&#xff0c;表示获取元神操作系统的版本号&#xff1b;第二个参数…

pdf怎么压缩小一些?推荐的几种PDF压缩方法

pdf怎么压缩小一些&#xff1f;在工作中&#xff0c;我们经常处理PDF文件。大文件不仅存储麻烦&#xff0c;还会拖慢传输速度。因此&#xff0c;我们通常希望将这些文件压缩成更小的尺寸。压缩后的文件更便于分享和管理&#xff0c;适用于云存储、社交媒体或其他在线平台&#…

安装node版本管理工具(nvm)、利用nvm安装node

https://github.com/coreybutler/nvm-windows/releases 下载nvm-setup.zip 选择nvm安装路径&#xff0c;注意路径不要有空格和中文。 选择nodejs的安装路径&#xff0c;这里是在E:\nodejs位置创建一个快捷方式&#xff0c;真正的文件在nvm文件下的版本号文件中 点击next&a…

容性负载箱如何测量电容器的容量、电压、泄漏电流和ESR等参数?

容性负载箱是用于测量电容器参数的重要设备。它的主要功能是通过向电容器施加不同的负载&#xff0c;从而测量电容器的容量、电压响应、损耗等关键参数。 具体来说&#xff0c;容性负载箱可以通过以下方式测量电容器的各项参数&#xff1a; 1. 测量电容器的容量&#xff1a;容…

并发工具类(二):CyclicBarrier

1、CyclicBarrier 介绍 从字面上看 CyclicBarrier 就是 一个循环屏障&#xff0c;它也是一个同步助手工具&#xff0c;它允许多个线程 在执行完相应的操作后彼此等待共同到达一个屏障点。 CyclicBarrier可以被循环使用&#xff0c;当屏障点值变为0之后&#xff0c;可以在接下来…

qt配合halcon深度学习网络环境配置

1.开发环境qt6&#xff0c;编译器MSCV2019&#xff0c;网络是halcon的对象检测&#xff0c;halcon用20. 2.建立qt项目 3.到halcon安装目录下复制include,lib这两个文件夹到qt项目中进行引用 4.引用到halcon静态库后&#xff0c;到halcon运行目录下找到静态库对应dll文件&…

浏览器百科:网页存储篇-如何在Chrome打开localStorage窗格(五)

1.引言 在前面的章节中&#xff0c;我们详细介绍了 localStorage 的基本概念、特性及其常用方法&#xff0c;帮助开发者在网页应用中实现数据的持久化存储。为了更好地管理和调试这些存储的数据&#xff0c;了解如何打开和使用浏览器的 localStorage 窗格是非常重要的。本篇文…

js实现lua解释器,类似halcon代码编辑器一行一行解释执行

解释器 只能一行一行执行&#xff0c;不能有一行代码跨越多行&#xff0c;不支持lua的表&#xff0c;只支持for i的循环&#xff0c;支持自定义函数&#xff0c;并且可以跳到函数里面一行一行执行&#xff0c;这里的函数并不是lua的函数&#xff0c;而是由js状态控制执行函数里…

DBETX-1X/250G24-8NZ4M比例溢流阀配套HE-SP1比例放大器

0811402019|DBETX-1X/250G24-8NZ4M比例溢流阀配套HE-SP1比例放大器主要是一种电液控制技术&#xff0c;用于调节液压系统中的压力&#xff0c;通过BEUEC比例放大器电气输入信号控制阀口的开度&#xff0c;实现对系统压力的精准控制。 比例溢流阀技术的关键在于其能够将电信号转…

振动分析-26-频域分析之深入理解功率谱和功率谱密度的计算过程

1 什么是PSD(功率谱密度) 功率谱密度(Power Spectral Density),以及其与Autopower(自功率谱)的区别。 1.1 PSD的定义 PSD——Power Spectral Density是表征信号的功率能量与频率的关系的物理量。 PSD经常用来研究随机振动信号。 PSD通常根据频率分辨率做归一化。 对于振…

Qt人脸识别与分析系统

项目源码地址https://github.com/fufufu11/QT5-FacialDetection 项目概述 本项目是一款基于Qt5框架构建的人脸检测应用程序&#xff0c;支持多摄像头选择和用户友好的图形界面。系统集成了百度的人脸检测API&#xff0c;能够通过HTTPS协议POST方法安全地发送请求&#xff0c;并…

一个基于共享内存的内存数据库:2 设计

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

opencv轮廓近似,模板匹配

在图像处理领域&#xff0c;轮廓近似和模板匹配是两种非常关键的技术&#xff0c;它们广泛应用于计算机视觉、图像分析和图像识别等多个方面。本文将详细介绍如何使用OpenCV库进行轮廓近似和模板匹配&#xff0c;并给出具体的代码示例。 一、轮廓近似&#xff08;Contour Appr…

使用stripe进行在线支付、退款、订阅、取消订阅功能(uniapp+h5)

stripe官网:Stripe 登录 | 登录 Stripe 管理平台 然后在首页当中打开测试模式,使用测试的公钥跟私钥进行开发 测试卡号 4242 4242 4242 4242 1234 567 在线支付 stripe的在线支付有两种,第一种就是无代码,第二中就是使用api进行自定义,一般来说推荐第二种进行开发 无…

谁还只会用OBS?多场景录制试试这四款!

很多人在录屏的时候&#xff0c;尤其是打游戏的朋友&#xff0c;第一时间想到的都是OBS&#xff0c;其实除了这款工具&#xff0c;还有很多好用的第三方录屏工具&#xff0c;一样可以帮助我们录制出不卡顿的高清视频。今天&#xff0c;我们就来对比一下市面上四款热门的录屏软件…