【问题标题】:Execute a application and wait for it to be loaded执行一个应用程序并等待它被加载
【发布时间】:2013-04-09 15:44:58
【问题描述】:

我正在寻找一种方法来执行 Steam (Steam.exe) 并等待它被加载(不退出)。

我认为一个好主意是列出所有子句柄并在标题句柄中搜索一个字符串,因为当 Steam 加载存在一个名为“Friends”的窗口句柄时,但这对我来说很难(API)所以也许存在一种等待程序加载的简单方法......

我将要做的示例:

' First process
Process.start("Process 1.exe")

' Here will goes an efficient method to wait for application is loaded,
' not sleeping X seconds measuring the loading time or any of that.

' Second process
' Depends on first process fully loaded.
' If first process is not fully loaded (libraries and that) then this process can't run.
Process.start("Processs 2.exe")

更新:

主要问题是如何等待 X 进程被加载,但关于 Steam.exe 我也注意到,当 Steam 加载时尝试读取/打开一些注册表项:

http://img267.imageshack.us/img267/6599/prtscrcapture4u.jpg

也许我可以用代码来监控其中一个 RegKeys 是否被访问?

像这样?:

Dim RegKey = "HKLM\...blablabla"
Process.start("steam.exe")

While not Regkey.IsAccessed 
  ' Do nothing and wait
Loop

Process.start("Second process.exe")

更新 2:

我正在尝试通过引用 Amegon 的解决方案来制作一个函数,我没有完成代码,因为我在该行中遇到了错误:

Memory = hprocess.PrivateMemorySize64

但我只有在尝试启动“大”应用程序(需要很长时间才能加载的应用程序,如 Photoshop)时才会收到错误消息,而“小”应用程序运行良好。

如果有人可以帮助我简化/改进 Amegon 的解决方案以制作通用函数并纠正内存溢出错误...谢谢!

这是我的代码:

' This is the sub that launchs the unfinished function:
Private Sub Launch_Game()
    'Wait_For_Application_To_Load("C:\Games\Steam.exe", "-applaunch 0")
    Wait_For_Application_To_Load("C:\Program Files\Adobe Photoshop CS6 (64 Bit)\Photoshop.exe")
End Sub

Private Function Wait_For_Application_To_Load(ByVal APP_Path As String, Optional ByVal APP_Arguments As String = Nothing)
    Dim File = My.Computer.FileSystem.GetFileInfo(APP_Path)
    Process.Start(APP_Path, APP_Arguments)
    Timer_CheckCPU.Tag = File.Name.Substring(0, File.Name.Length - 4) ' Photoshop
    Timer_CheckCPU.Enabled = True
End Function

Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Integer, ByVal lpBaseAddress As Integer, ByVal lpBuffer As Integer, ByVal nSize As Integer, ByRef lpNumberOfBytesWritten As Integer) As Integer
Private WithEvents Timer_CheckCPU As New Timer

Dim Memory_Value_Changed As Boolean
Dim CPU_Changed As Boolean
Dim CPU_Time As Boolean
Dim Running_Time As Boolean
Private _desiredTime_ms As Integer = 1500

Private Sub Timer_CheckCPU_Tick(sender As Object, ev As EventArgs) Handles Timer_CheckCPU.Tick
    Timer_CheckCPU.Enabled = False
    Dim pProcess() As Process = System.Diagnostics.Process.GetProcessesByName(Timer_CheckCPU.Tag)
    Dim hprocess As Process = pProcess(0)
    If hprocess Is Nothing Then
        Running = False
        Timer_CheckCPU.Enabled = True
        Return
    End If
    Running = True


    ' Here is the error:
    Memory = hprocess.PrivateMemorySize64
    ' MsgBox(hprocess.PrivateMemorySize64.ToString)


    CPUTotal = hprocess.TotalProcessorTime.TotalMilliseconds

    If AllConditionsGood() Then
        If Not (_countdown.IsRunning) Then
            _countdown.Reset()
            _countdown.Start()
        End If
        Dim _elapsed As Integer = _countdown.ElapsedMilliseconds
        If _elapsed >= _desiredTime_ms Then
            Me.TopMost = True
            MsgBox("process loaded")
            Return
        End If
    Else
        _countdown.Reset()
    End If
    Timer_CheckCPU.Enabled = True
End Sub

Private Function AllConditionsGood() As Boolean
    If CPU_Time Then Return False
    If Memory_Value_Changed Then Return False
    If Running_Time Then Return False
    Return True
End Function

Private _countdown As New Stopwatch

Private _Running As Boolean = False
Public WriteOnly Property Running() As Boolean
    Set(ByVal value As Boolean)
        _Running = value
        If value Then
            Running_Time = False
        Else
            Running_Time = True
        End If
    End Set
End Property

Private _CPUTotal As Integer
Public WriteOnly Property CPUTotal() As Integer
    Set(ByVal value As Integer)
        CPU = value - _CPUTotal 'used cputime since last check
        _CPUTotal = value
    End Set
End Property


Private _CPU As Integer
Public WriteOnly Property CPU() As Integer
    Set(ByVal value As Integer)
        If value = 0 Then
            CPU_Time = False
        Else
            CPU_Time = True
        End If
        _CPU = value
    End Set
End Property

Private _Memory As Integer
Public WriteOnly Property Memory() As Integer
    Set(ByVal value As Integer)
        MemoryDiff = Math.Abs(value - _Memory)
        _Memory = value
    End Set
End Property

Private _MemoryDiff As Integer
Public WriteOnly Property MemoryDiff() As Integer
    Set(ByVal value As Integer)
        If value = _MemoryDiff Then
            Memory_Value_Changed = False
        Else
            Memory_Value_Changed = True
        End If
        _MemoryDiff = value
    End Set
End Property

【问题讨论】:

  • 你检查过Process类的GetProcessesByName方法吗?只需尝试使用 ProcessExplorer 之类的工具来识别进程名称.....
  • 如果您单击“处理”按钮,您会看到什么?您正在搜索进程而不是窗口
  • 对不起,我是第一次使用 spy++,我已经编辑了我的问题!
  • 我不确定是否可以使用 vb.net 监控其他程序 reg 密钥访问,但检查正在运行的窗口和这些窗口的子进程并不难。但我想任何事情都可能奏效,只要它能很好地提示加载已完成。由于好友列表自动登录可以被禁用,如果您扫描打开的好友列表,这将不是一个通用的解决方案
  • 不,我的第一个想法是在加载 Steam 时扫描标题为“好友”的句柄,但该句柄并不指好友列表窗口自动登录(我不知道具体是哪个是引用),因为如果我使用任何自动日志选项(服务器、朋友、社区、商店等),句柄就存在。

标签: vb.net visual-studio handle window-handles


【解决方案1】:

WaitForInputIdle 方法一直等到应用程序启动并等待输入

Dim psi As New ProcessStartInfo("fileName", "arguments")
Dim p As New Process
p.StartInfo = psi
p.Start()
p.WaitForInputIdle()

注意:这不适用于服务应用程序。

【讨论】:

  • 等待输入就意味着等待用户输入?是仅适用于 CLI 应用的解决方案吗?
  • +1 用于启动进程的方式(并立即拥有它的句柄,不知道那种方式)。此外,如果您保留进程句柄 p 并且 WaitForInputIdle 未成功(例如,steam 允许用户输入,而它仍在加载一些组件,例如登录到朋友网络),那么您可以使用 .Refresh 来读取最新的值进程句柄 p.
  • @Amegon:是的,您只能致电WaitForInputIdle 一次!以后就不会再有任何影响了。我不知道这个方法究竟是如何工作的,但我认为它正在等待消息循环开始,即你可以用SendKeys 发送一些东西,例如,即使它仍在工作。
  • @ElektroHacker:这应该适用于 WinForms 应用程序。
  • @OlivierJacot-Descombes 是的,msdn help 说它会冻结,直到进程在消息循环中处于“空闲”状态。这也是为什么它只适用于带有消息队列的应用程序,但这应该适用于这里的任何 gui 应用程序。但是,如果 gui 在另一个窗口仍在加载/连接时接受用户输入,则在未完全加载时仍可能触发。当好友列表窗口正在连接好友网络时,您可以点击 Steam 库。您也可以使用 WaitForInputIdle(timeout_in_ms) 并将其放入循环中以在等待时提供一些反馈
【解决方案2】:

更新我已经构建了一个示例程序来显示这个内存和 cpu 观察

要运行它,创建一个新项目,windows 窗体应用程序,然后打开代码部分并插入以下代码。 - 无需添加任何控件,我在代码中手动添加了它们。我能够在 win8 prof x64- 上使用 VS 2012 Ultimate 成功运行代码:

Option Strict On
Imports System.Runtime.InteropServices

Public Class Form1
    Private WithEvents btnStart As New Button
    Private WithEvents tmrCheckCPU As New Timer

    Private tbRunning As New TextBox
    Private tbCPU As New TextBox
    Private tbMemory As New TextBox
    Private tbTime As New TextBox

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.Size = New Size(400, 400)
        With btnStart
            '.Text = "Start DxDiag"
            .Text = "Start Photoshop"
            Me.Controls.Add(btnStart)
            .Location = New Point(50, 50)
            .Size = New Size(200, 50)
        End With
        With tmrCheckCPU
            .Interval = 100
            .Enabled = False
        End With
        With tbRunning
            Me.Controls.Add(tbRunning)
            .Location = New Point(50, 110)
            .Size = New Size(100, 40)
        End With
        With tbCPU
            Me.Controls.Add(tbCPU)
            .Location = New Point(50, 150)
            .Size = New Size(100, 40)
        End With
        With tbMemory
            Me.Controls.Add(tbMemory)
            .Location = New Point(50, 200)
            .Size = New Size(100, 40)
        End With
        With tbTime
            Me.Controls.Add(tbTime)
            .Location = New Point(50, 250)
            .Size = New Size(100, 40)
        End With
    End Sub

    Private Sub btnStart_Clicked(sender As Object, ev As EventArgs) Handles btnStart.Click
        'start Process
        'Process.Start("dxdiag.exe", "/whql:on")
        Process.Start("C:\Program Files\Adobe\Adobe Photoshop CS6 (64 Bit)\Photoshop.exe")
        'start watching Timer
        tmrCheckCPU.Enabled = True
    End Sub

    Private Function FindProcess(MainWindowTitle As String) As System.Diagnostics.Process
        For Each ele As System.Diagnostics.Process In Process.GetProcesses
            If ele.MainWindowTitle = MainWindowTitle Then Return ele
        Next
        Return Nothing
    End Function

    Private Sub tmrCheckCPU_Tick(sender As Object, ev As EventArgs) Handles tmrCheckCPU.Tick
        tmrCheckCPU.Enabled = False
        'Dim name As String = "DirectX Diagnostic Tool"
        Dim name As String = "Adobe Photoshop CS6 Extended"
        Dim hprocess As Process = FindProcess(name)
        If hprocess Is Nothing Then
            Running = False
            tmrCheckCPU.Enabled = True
            Return
        End If
        Running = True
        Memory = hprocess.PrivateMemorySize64
        CPUTotal = hprocess.TotalProcessorTime.TotalMilliseconds

        If AllConditionsGood() Then
            If Not (_countdown.IsRunning) Then
                _countdown.Reset()
                _countdown.Start()
            End If
            Dim _elapsed As Long = _countdown.ElapsedMilliseconds
            If _elapsed >= _desiredTime_ms Then
                tbTime.Text = _desiredTime_ms.ToString
                tbTime.BackColor = Color.LightGreen
                Me.TopMost = True
                Me.Focus()
                MsgBox("process loaded")
                Return
            Else
                tbTime.Text = _elapsed.ToString
                tbTime.BackColor = Color.LightGreen
            End If
        Else
            _countdown.Reset()
        End If
        tmrCheckCPU.Enabled = True
    End Sub

    Private _desiredTime_ms As Integer = 1500

    Private Function AllConditionsGood() As Boolean
        If tbCPU.BackColor <> Color.LightGreen Then Return False
        If tbMemory.BackColor <> Color.LightGreen Then Return False
        If tbRunning.BackColor <> Color.LightGreen Then Return False
        Return True
    End Function

    Private _countdown As New Stopwatch

    Private _Running As Boolean = False
    Public WriteOnly Property Running() As Boolean
        Set(ByVal value As Boolean)
            _Running = value
            If value Then
                tbRunning.Text = "Running"
                tbRunning.BackColor = Color.LightGreen
            Else
                tbRunning.Text = "Not Running"
                tbRunning.BackColor = Color.LightPink
            End If
        End Set
    End Property

    Private _CPUTotal As Double
    Public WriteOnly Property CPUTotal() As Double
        Set(ByVal value As Double)
            CPU = value - _CPUTotal 'used cputime since last check
            _CPUTotal = value
        End Set
    End Property


    Private _CPU As Double
    Public WriteOnly Property CPU() As Double
        Set(ByVal value As Double)
            If value = 0 Then
                tbCPU.BackColor = Color.LightGreen
            Else
                tbCPU.BackColor = Color.LightPink
            End If
            _CPU = value
            tbCPU.Text = value.ToString
        End Set
    End Property

    Private _Memory As Long
    Public WriteOnly Property Memory() As Long
        Set(ByVal value As Long)
            MemoryDiff = Math.Abs(value - _Memory)
            _Memory = value
        End Set
    End Property

    Private _MemoryDiff As Long
    Public WriteOnly Property MemoryDiff() As Long
        Set(ByVal value As Long)
            If value = _MemoryDiff Then
                tbMemory.BackColor = Color.LightGreen
            Else
                tbMemory.BackColor = Color.LightPink
            End If
            _MemoryDiff = value
            tbMemory.Text = value.ToString
        End Set
    End Property

End Class

如果你点击按钮,它将启动 dxdiag 并在文本框中显示内存的变化和总 cpu 时间的差异(与上次相比)。如果满足所有条件(正在运行,没有 cpu 总时间变化,没有内存变化),那么秒表将计数到 1500,然后消息框会显示“完成”

写的很脏,不过我猜你看懂了主要核心(如何在没有任何dll声明方法的情况下找到正确的进程),找到后如何读取该进程的一些信息,以及以什么方式应用条件.

等待时间是为了确保一秒钟的纯硬盘加载时间不会造成加载完成的错误印象(可能没有内存或 CPU 变化)。

风险在于.. 如果一个页面会自动出现并显示内容/广告,那么可能 cpu 一直在使用。因此您只能检查内存更改或您可以从进程中访问的任何其他值。

更新更正了代码(修复了错误的数据类型)。现在 Option strict 没有抱怨我的代码:)

【讨论】:

  • 谢谢,但是在第一个进程(steam.exe)的加载时间中内存消耗可能会有很大差异,我认为这就像时间总结一样有风险。真的,我不明白您对“子进程”(steam.exe 的子线程,或独立的可执行文件)的确切含义,但我已经使用“sysinternals”的“进程监视器”监视了可执行文件,我'现在第一次使用“Spy++”,我没有注意到任何“子进程”,但也许我错了,因为我不是专家。我会在几秒钟内更新我的问题并上传截图。
  • 关于消失,我用一个参数启动steam来禁用更新,这个问题就解决了。
  • 我也是 Spy++ 的初学者,但是在使用 sendkeys(尝试构建一个简单的机器人)时,我了解到有时必须访问特定目标才能发送命令。因此,使用 spy++ 检查作为一个正在运行的程序还存在什么是很有用的。我不确定它是定义为子进程还是子线程。由于steam可能不会发送消息“我已完成加载”,也许您可​​以尝试在steam下找到另一个仅在加载时才存在的子“对象”,例如显示朋友。在这种情况下,您可以不断扫描该对象并知道发现时已加载蒸汽
  • 哦,您对内存的更新似乎很好,但是在 Steam 最小化和后台运行时,我可以看到内存值在每 2-30 秒之间如何变化,这个应用程序的最佳解决方案似乎是正在检查句柄标题“朋友”,但我尝试使用 WinAPI,但我不能,无论如何我会尝试像其他应用程序的通用解决方案一样的内存方式,谢谢。
  • 根据您读取的内存信息,您可能仅会在更改 max-memory-border 时看到更改。我认为通常某些领域会加倍,因此不会经常更改。根据msdn.microsoft.com/en-us/library/ccf1tfx0%28v=vs.110%29.aspx 的帮助,查找不返回 max 内存大小的内存属性。这应该有助于看到更多变化。
猜你喜欢
  • 2016-06-07
  • 2011-04-22
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
  • 2020-12-12
  • 2022-12-18
  • 2012-02-06
  • 1970-01-01
相关资源
最近更新 更多