【问题标题】:CreateProcess with CREATE_SUSPENDED cannot be ResumeThread-ed具有 CREATE_SUSPENDED 的 CreateProcess 不能被 ResumeThread-ed
【发布时间】:2021-03-17 00:30:51
【问题描述】:

我无法使用 WINAPI 的 CreateProcess 和 CREATE_SUSPENDED 和 VBA 的 ResumeThread。

我想启动一个进程(并接收它的进程 ID)并能够暂停和恢复其主线程(取决于考虑到我计算机的资源利用率使用的更复杂的方案 - 此处不详述)。我想出了以下代码并遇到以下问题:

  1. 调用 CreateProcess 后 LastDllError 为 18 虽然返回 值非零。这是什么意思?

  2. ResumeThread 失败并出现 ERROR_INVALID_HANDLE,并且不会恢复 它。这里有什么问题?

我的代码:

Option Explicit


Private Type SECURITY_ATTRIBUTES
    nLength              As Long
    lpSecurityDescriptor As Long
    bInheritHandle       As Long
End Type

Private Type STARTUPINFO
    cb              As Long
    lpReserved      As String
    lpDesktop       As String
    lpTitle         As String
    dwX             As Long
    dwY             As Long
    dwXSize         As Long
    dwYSize         As Long
    dwXCountChars   As Long
    dwYCountChars   As Long
    dwFillAttribute As Long
    dwFlags         As Long
    wShowWindow     As Integer
    cbReserved2     As Integer
    lpReserved2     As Byte
    hStdInput       As Long
    hStdOutput      As Long
    hStdError       As Long
End Type

Private Type PROCESS_INFORMATION
    hProcess    As Long
    hThread     As Long
    dwProcessId As Long
    dwThreadId  As Long
End Type

Private Const CREATE_SUSPENDED As Long = 4

Private Declare Function CreateProcess Lib "kernel32" Alias "CreateProcessA" ( _
       ByVal lpApplicationName As String, _
       ByVal lpCommandLine As String, _
       ByRef lpProcessAttributes As SECURITY_ATTRIBUTES, _
       ByRef lpThreadAttributes As SECURITY_ATTRIBUTES, _
       ByVal bInheritHandles As Long, _
       ByVal dwCreationFlags As Long, _
       ByRef lpEnvironment As Any, _
       ByVal lpCurrentDirectory As String, _
       ByRef lpStartupInfo As STARTUPINFO, _
       ByRef lpProcessInformation As PROCESS_INFORMATION) As Long

Private Declare Function SuspendThread Lib "kernel32" (hThread As Long) As Long

Private Declare Function ResumeThread Lib "kernel32" (hThread As Long) As Long

Private Declare Function CloseHandle Lib "kernel32.dll" (ByVal hObject As Long) As Long

Private Declare Function DebugActiveProcess Lib "kernel32" (ByVal dwProcessId As Long) As Long

Private Declare Function DebugActiveProcessStop Lib "kernel32" (ByVal dwProcessId As Long) As Long

Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)


Public Function WinApi_CreateProcess(strCommandLine As String, Optional strCurrentDirectory As String = vbNullString) As Long
    If strCurrentDirectory = vbNullString Then
        strCurrentDirectory = ThisWorkbook.Path
    End If
    Dim sap As SECURITY_ATTRIBUTES: sap.nLength = Len(sap)
    Dim sat As SECURITY_ATTRIBUTES: sat.nLength = Len(sat)
    Dim si As STARTUPINFO: si.cb = Len(si)
    Dim pi As PROCESS_INFORMATION
    Debug.Print Err.LastDllError ' 0 => ERROR_SUCCESS
    Dim dwResult As Long: dwResult = CreateProcess(vbNullString, strCommandLine, sap, sat, 0, CREATE_SUSPENDED, 0, strCurrentDirectory, si, pi)
    Debug.Print Err.LastDllError ' 18 => ERROR_NO_MORE_FILES (but dwResult <> 0 => Success)
    If dwResult = 0 Then
        WinApi_CreateProcess = 0: Exit Function
    End If
    CloseHandle pi.hProcess
    Debug.Print Err.LastDllError ' 0 => ERROR_SUCCESS
    Dim dwSuspendCount As Long: dwSuspendCount = ResumeThread(pi.hThread)
    Debug.Print dwSuspendCount ' -1
    If dwSuspendCount = -1 Then
        Debug.Print Err.LastDllError ' 6 => ERROR_INVALID_HANDLE
        CloseHandle pi.hThread
        WinApi_CreateProcess = 0: Exit Function
    Else
        Debug.Print Err.LastDllError ' Not this branch
        CloseHandle pi.hThread
        WinApi_CreateProcess = pi.dwProcessId: Exit Function
    End If
End Function

【问题讨论】:

  • 告诉您 hThread 字段包含无效的句柄值。它会在 VBA 代码在 64 位模式下运行时,句柄是 LongPtr。碰巧对 hProcess 起作用,因为它是结构中的第一个字段。
  • @HansPassant 这怎么可能?我有一台 x64 计算机,但我的 Office 是 32 位的,我创建的进程也是 32 位的。 (在任务管理器中我看到EXCEL.EXE *32cmd.exe *32。后者的路径是C:\Windows\SysWOW64\cmd.exe。)
  • 好的,下一个bug是STARTUPINFO.lpReserved2,它是一个指针。 So Long 或 LongPtr,而不是 Byte。当操作系统写入太多数据而不适合变量中的空间时,这会导致内存损坏,从而影响相邻的变量。这恰好是这段代码中的 pi 变量。还不足以破坏 hThread 字段,但鞋子很合适。
  • 谢谢,我更正了那个。但是,它并没有解决它。由于操作系统只从 si 中读取数据,因此不会损坏 pi。

标签: vba winapi createprocess


【解决方案1】:

调用 CreateProcess 后 LastDllError 为 18,尽管返回值非零。这是什么意思?

这意味着您错误地使用了Err.LastDllError。如果CreateProcess() 成功(返回非零),Err.LastDllError 的值是indeterminate,所以忽略它。只有当CreateProcess() 失败(返回零)时,它的值才有意义。

ResumeThread 失败并显示 ERROR_INVALID_HANDLE,并且不会恢复它。这里有什么问题?

您错误地检查了ResumeThread() 的返回值,因此您又在错误的时间检查了Err.LastDllError

根据ResumeThread() documentation

如果函数成功,则返回值是线程之前的挂起计数

如果函数失败,返回值为 (DWORD) -1。 要获取扩展的错误信息,请调用 GetLastError。

在这种情况下,您正在检查 ResumeThread() 的返回值是否为 0,但该进程是在挂起状态下创建的,因此其主线程的挂起计数将为 1,因此 ResumeThread() 应该返回 1,如果线程已成功恢复,但您将其视为失败条件而不是成功条件。

你需要改变这个:

If ResumeThread(pi.hThread) &lt;&gt; 0 Then

到这里:

If ResumeThread(pi.hThread) = -1 Then

并清理您对Err.LastDllError 的使用,例如:

Public Function WinApi_CreateProcess(strCommandLine As String, Optional strCurrentDirectory As String = vbNullString) As Long
    If strCurrentDirectory = vbNullString Then
        strCurrentDirectory = ThisWorkbook.Path
    End If
    Dim sap As SECURITY_ATTRIBUTES: sap.nLength = Len(sap)
    Dim sat As SECURITY_ATTRIBUTES: sat.nLength = Len(sat)
    Dim si As STARTUPINFO: si.cb = Len(si)
    Dim pi As PROCESS_INFORMATION
    Dim dwResult As Long: dwResult = CreateProcess(vbNullString, strCommandLine, sap, sat, 0, CREATE_SUSPENDED, 0, strCurrentDirectory, si, pi)
    If dwResult = 0 Then
        Debug.Print Err.LastDllError
        WinApi_CreateProcess = 0: Exit Function
    End If
    CloseHandle pi.hProcess
    Dim dwSuspendCount As Long: dwSuspendCount = ResumeThread(pi.hThread)
    If dwSuspendCount = -1 Then
        Debug.Print Err.LastDllError
        CloseHandle pi.hThread
        WinApi_CreateProcess = 0: Exit Function
    End If
    CloseHandle pi.hThread
    WinApi_CreateProcess = pi.dwProcessId
End Function

但是,您真的不需要创建一个暂停的进程然后再恢复它来获取它的进程 ID。完全摆脱CREATE_SUSPENDEDResumeThread(),在这种情况下您实际上不需要它们:

Public Function WinApi_CreateProcess(strCommandLine As String, Optional strCurrentDirectory As String = vbNullString) As Long
    If strCurrentDirectory = vbNullString Then
        strCurrentDirectory = ThisWorkbook.Path
    End If
    Dim sap As SECURITY_ATTRIBUTES: sap.nLength = Len(sap)
    Dim sat As SECURITY_ATTRIBUTES: sat.nLength = Len(sat)
    Dim si As STARTUPINFO: si.cb = Len(si)
    Dim pi As PROCESS_INFORMATION
    Dim dwResult As Long: dwResult = CreateProcess(vbNullString, strCommandLine, sap, sat, 0, 0, 0, strCurrentDirectory, si, pi)
    If dwResult = 0 Then
        Debug.Print Err.LastDllError
        WinApi_CreateProcess = 0: Exit Function
    End If
    CloseHandle pi.hThread
    CloseHandle pi.hProcess
    WinApi_CreateProcess = pi.dwProcessId
End Function

【讨论】:

  • 你从哪里读到 Err.LastDllError 是不确定的,如果之前的 API 调用成功了?我的经验是它被设置回 0,除非 Windows 想要提供一些扩展信息
  • 您对 ResumeThread 的返回值是正确的,我已经更新了我的代码。然而,这并没有解决问题。 ResumeThread 返回 -1,Err.LastDllError 为 6。
  • 我想创建暂停的进程。
  • @z32a7ul "你从哪里读到 Err.LastDllError 是不确定的,如果之前的 API 调用成功了?" - Err.LastDllError 只是 GetLastError() 的包装。每个 API 函数的文档都会告诉您 GetLastError() 何时可以使用。对于大多数API,包括CreateProcess(),错误码只有在API报错时才有效。大多数 API 在成功时不会重置或更新错误代码,因此陈旧的错误代码可以通过多个 API 调用传播。
  • @z32a7ul "ResumeThread 返回-1,并且 Err.LastDllError 为 6。" - 你在调用Debug.Print 后检查Err.LastDllError,这可以改变错误代码来自ResumeThread()。在报告错误之后,进行另一个系统调用之前,您必须立即阅读Err.LastDllError
【解决方案2】:

在将所有指针类型修改为LongPtr 而不是Long 后,我可以使用示例重现您在 64 位 Excel 中的问题。也可以参考here的声明

Private Type SECURITY_ATTRIBUTES
    nLength              As Long
    lpSecurityDescriptor As LongPtr
    bInheritHandle       As Long
End Type

Private Type STARTUPINFO
    cb              As Long
    lpReserved      As String
    lpDesktop       As String
    lpTitle         As String
    dwX             As Long
    dwY             As Long
    dwXSize         As Long
    dwYSize         As Long
    dwXCountChars   As Long
    dwYCountChars   As Long
    dwFillAttribute As Long
    dwFlags         As Long
    wShowWindow     As Integer
    cbReserved2     As Integer
    lpReserved2     As LongPtr
    hStdInput       As LongPtr
    hStdOutput      As LongPtr
    hStdError       As LongPtr
End Type

Private Type PROCESS_INFORMATION
    hProcess    As LongPtr
    hThread     As LongPtr
    dwProcessId As Long
    dwThreadId  As Long
End Type

然后我得到了CreateProcesstrue 结果和ResumeThreadERROR_INVALID_HANDLE 错误,我发现ResumeThread 参数没有用ByVal 声明(与SuspendThread 相同)。添加ByVal 后,该示例适用于我。

Declare PtrSafe Function SuspendThread Lib "kernel32" (ByVal hThread As LongPtr) As Long

Declare PtrSafe Function ResumeThread Lib "kernel32" (ByVal hThread As LongPtr) As Long

【讨论】:

  • 谢谢,你的答案有点难以理解,但本质是 ResumeThread 和 SuspendThread 的参数应该用 ByVal 声明。 (否则 VBA 将其视为 ByRef,因此不是它的值,而是保存该值的变量的地址被提供给 WinAPI。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-12
  • 2012-06-28
  • 1970-01-01
  • 2021-01-27
相关资源
最近更新 更多