CREATE_SUSPENDED 的 CreateProcess 不能被 ResumeThread 编辑

CreateProcess with CREATE_SUSPENDED cannot be ResumeThread-ed

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

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

  1. LastDllError 在调用 CreateProcess 后为 18,尽管 return 值为非零。这是什么意思?

  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

LastDllError is 18 after calling CreateProcess although the return value is nonzero. What does this mean?

这意味着您使用的 Err.LastDllError 不正确。如果CreateProcess()成功(returns非零),Err.LastDllError的值为不确定,所以忽略它。它的值只有在 CreateProcess() 失败 (return 为零)时才有意义。

ResumeThread fails with ERROR_INVALID_HANDLE, and does not resume it. What is wrong here?

您错误地检查了 ResumeThread() 的 return 值,因此您再次在错误的时间检查了 Err.LastDllError

根据 ResumeThread() documentation:

If the function succeeds, the return value is the thread's previous suspend count.

If the function fails, the return value is (DWORD) -1. To get extended error information, call GetLastError.

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

你需要改变这个:

If ResumeThread(pi.hThread) <> 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

将所有指针类型从 Long 修改为 LongPtr 后,我可以使用该示例在 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 参数没有用 [=19 声明=](与 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