【问题标题】:How to read output from cmd.exe using CreateProcess() and CreatePipe()如何使用 CreateProcess() 和 CreatePipe() 从 cmd.exe 读取输出
【发布时间】:2016-03-13 11:35:54
【问题描述】:

如何使用 CreateProcess() 和 CreatePipe() 从 cmd.exe 读取输出

我一直在尝试创建一个执行cmd.exe 的子进程,其命令行指定/K dir。目的是使用管道将命令的输出读回父进程。

我已经让CreateProcess() 工作了,但是涉及管道的步骤给我带来了麻烦。使用管道,新的控制台窗口没有显示(就像以前一样),并且父进程卡在对ReadFile()的调用中。

有人知道我做错了什么吗?

#include <Windows.h>
#include <stdio.h>
#include <tchar.h>

#define BUFFSZ 4096

HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;

int wmain(int argc, wchar_t* argv[]) 
{
    int result;
    wchar_t aCmd[BUFFSZ] = TEXT("/K dir"); // CMD /?
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    SECURITY_ATTRIBUTES sa;

    printf("Starting...\n");

    ZeroMemory(&si, sizeof(STARTUPINFO));
    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
    ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));

    // Create one-way pipe for child process STDOUT
    if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0)) {
        printf("CreatePipe() error: %ld\n", GetLastError());
    }

    // Ensure read handle to pipe for STDOUT is not inherited
    if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) {
        printf("SetHandleInformation() error: %ld\n", GetLastError());
    }

    // Create one-way pipe for child process STDIN
    if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &sa, 0)) {
        printf("CreatePipe() error: %ld\n", GetLastError());
    }

    // Ensure write handle to pipe for STDIN is not inherited
    if (!SetHandleInformation(g_hChildStd_IN_Rd, HANDLE_FLAG_INHERIT, 0)) {
        printf("SetHandleInformation() error: %ld\n", GetLastError());
    }

    si.cb = sizeof(STARTUPINFO);
    si.hStdError = g_hChildStd_OUT_Wr;
    si.hStdOutput = g_hChildStd_OUT_Wr;
    si.hStdInput = g_hChildStd_IN_Rd;
    si.dwFlags |= STARTF_USESTDHANDLES;

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    // Pipe handles are inherited
    sa.bInheritHandle = true;

    // Creates a child process
    result = CreateProcess(
        TEXT("C:\\Windows\\System32\\cmd.exe"),     // Module
        aCmd,                                       // Command-line
        NULL,                                       // Process security attributes
        NULL,                                       // Primary thread security attributes
        true,                                       // Handles are inherited
        CREATE_NEW_CONSOLE,                         // Creation flags
        NULL,                                       // Environment (use parent)
        NULL,                                       // Current directory (use parent)
        &si,                                        // STARTUPINFO pointer
        &pi                                         // PROCESS_INFORMATION pointer
        );

    if (result) {
        printf("Child process has been created...\n");
    }
    else {
        printf("Child process could not be created\n");
    }

    bool bStatus;
    CHAR aBuf[BUFFSZ + 1];
    DWORD dwRead;
    DWORD dwWrite;
    // GetStdHandle(STD_OUTPUT_HANDLE)

    while (true) {
        bStatus = ReadFile(g_hChildStd_OUT_Rd, aBuf, sizeof(aBuf), &dwRead, NULL);
        if (!bStatus || dwRead == 0) {
            break;
        }
        aBuf[dwRead] = '\0';
        printf("%s\n", aBuf);
    }

        // Wait until child process exits
        WaitForSingleObject(pi.hProcess, INFINITE);

        // Close process and thread handles
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);

        printf("Stopping...\n");

        return 0;
    }

【问题讨论】:

  • 代码只完成了一半,死锁在所难免。 ReadFile() 将在进程填充其标准输出缓冲区之前完成,因此需要刷新或关闭句柄。缓冲区不会被填充,因为它只包含提示。并且没有添加任何内容,也不会关闭句柄,因为您没有告诉它做任何事情。不读取 stderr 也是一个可能导致死锁的错误,否则很容易通过告诉它合并 stdout 和 stderr 输出来避免。嗯,这就是为什么,修复它不是很有用。
  • 我该怎么做?我应该创建另一个线程吗?我该如何冲洗?
  • 不能强制刷新,是cmd.exe的stdout缓冲区。除非您将“exit\r”发送到标准输入。该程序设计为在您使用 /k 时以交互方式使用,重定向仅在您使用 /c 时才有效。修复此问题需要使用重叠 I/O,以便您可以同时异步读取 stderr 和 stdout,并使用 WaitForMultipleObjects() 以便您可以等待所有三个句柄。
  • 请注意,dwRead == 0 不是错误条件。来自ReadFile documentation“如果在管道上 ReadFile 返回 TRUE 时 lpNumberOfBytesRead 参数为零,则管道的另一端调用 WriteFile 函数,并将 nNumberOfBytesToWrite 设置为零。”

标签: c++ windows cmd


【解决方案1】:

解决问题的巧妙方法是确保关闭不需要的管道末端。

你的父进程有四个句柄:

  • 其中两个是管道的你的
    • g_hChildStd_IN_Wr
    • g_hChildStd_OUT_Rd
  • 其中两个是管道的 child 的
    • g_hChildStd_IN_Rd
    • g_hChildStd_OUT_Wr

 

╔══════════════════╗                ╔══════════════════╗
║  Parent Process  ║                ║  Child Process   ║
╠══════════════════╣                ╠══════════════════╣
║                  ║                ║                  ║
║ g_hChildStd_IN_Wr╟───────────────>║g_hChildStd_IN_Rd ║ 
║                  ║                ║                  ║ 
║g_hChildStd_OUT_Rd║<───────────────╢g_hChildStd_OUT_Wr║
║                  ║                ║                  ║
╚══════════════════╝                ╚══════════════════╝

您的父进程只需要每个管道的一个端:

  • 子输入管道的可写端:g_hChildStd_IN_Wr
  • 子输出管道的可读端:g_hChildStd_OUT_Rd

一旦您启动了您的子进程:请务必关闭您不再需要的管道末端:

  • CloseHandle(g_hChildStd_IN_Rd)
  • CloseHandle(g_hChildStd_OUT_Wr)

离开:

╔══════════════════╗                ╔══════════════════╗
║  Parent Process  ║                ║  Child Process   ║
╠══════════════════╣                ╠══════════════════╣
║                  ║                ║                  ║
║ g_hChildStd_IN_Wr╟───────────────>║                  ║ 
║                  ║                ║                  ║ 
║g_hChildStd_OUT_Rd║<───────────────╢                  ║
║                  ║                ║                  ║
╚══════════════════╝                ╚══════════════════╝

或更全面:

STARTUP_INFO si;
PROCESS_INFO pi;
result = CreateProcess(..., ref si, ref pi);

//Bonus chatter: A common bug among a lot of programmers: 
// they don't realize they are required to call CloseHandle 
// on the two handles placed in PROCESS_INFO.
// That's why you should call ShellExecute - it closes them for you.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

/*
   We've given the console app the writable end of the pipe during CreateProcess; we don't need it anymore.
   We do keep the handle for the *readable* end of the pipe; as we still need to read from it.
   The other reason to close the writable-end handle now is so that there's only one out-standing reference to the writeable end: held by the child process.
   When the child processes closes, it will close the pipe, and 
   your call to ReadFile will fail with error code:
      109 (The pipe has been ended).

   That's how we'll know the console app is done. (no need to wait on process handles with buggy infinite waits)
*/
CloseHandle(g_hChildStd_OUT_Wr);
g_hChildStd_OUT_Wr = 0;
CloseHandle(g_hChildStd_IN_Rd);
g_hChildStd_OUT_Wr = 0;

等待子进程(也就是等待发生的死锁)

大多数解决方案的常见问题是people try 等待进程句柄。

  • 他们创建事件对象
  • 他们尝试 MsgWait 等待事件发出信号
  • 他们尝试 MsgWait 等待子进程结束

错了。 完全错了

这些想法有很多问题;最主要的是:

  • 如果您尝试等待孩子终止
  • 孩子将永远无法终止

如果孩子试图通过管道向您发送输出,而您正在 INFINITE 等待,那么您并没有清空管道的末端。最终,孩子正在写信的管道变满了。当孩子尝试写入已满的管道时,它的WriteFile 调用等待块)让管道有一些空间。

  • 您被阻止等待孩子
  • 孩子尝试写入管道
  • 你被阻止等待孩子,所以你没有从管道中读取数据
  • 管道已满
  • 孩子们在等着你
  • 父母和孩子都被阻止等待对方
  • 死锁

因此,进程将永远终止;你把一切都锁死了。

正确的方法 - 让客户做它的事情

正确的解决方案来自简单地从管道中读取

  • 一旦子进程终止,
  • 它将CloseHandle 位于管道的端。
  • 下次您尝试从管道中读取时
  • 您会被告知管道已关闭 (ERROR_BROKEN_PIPE)。
  • 这就是您知道流程已完成并且您没有更多内容要阅读的方式。

 

String outputText = "";

//Read will return when the buffer is full, or if the pipe on the other end has been broken
while (ReadFile(stdOutRead, aBuf, Length(aBuf), &bytesRead, null)
   outputText = outputText + Copy(aBuf, 1, bytesRead);

//ReadFile will either tell us that the pipe has closed, or give us an error
DWORD le = GetLastError;

//And finally cleanup
CloseHandle(g_hChildStd_IN_Wr);
CloseHandle(g_hChildStd_OUT_Rd);

if (le != ERROR_BROKEN_PIPE) //"The pipe has been ended."
   RaiseLastOSError(le);

所有都没有危险的 MsgWaitForSingleObject - 这很容易出错,难以正确使用,并且会导致您想要避免的错误。

完整示例

我们都知道我们使用它的目的:运行一个子进程,并捕获它的控制台输出。

这里是一些示例 Delphi 代码:

function ExecuteAndCaptureOutput(CommandLine: string): string;
var
    securityAttributes: TSecurityAttributes;
    stdOutRead, stdOutWrite: THandle;
    startupInfo: TStartupInfo;
    pi: TProcessInformation;
    buffer: AnsiString;
    bytesRead: DWORD;
    bRes: Boolean;
    le: DWORD;
begin
    {
        Execute a child process, and capture it's command line output.
    }
    Result := '';

    securityAttributes.nlength := SizeOf(TSecurityAttributes);
    securityAttributes.bInheritHandle := True;
    securityAttributes.lpSecurityDescriptor := nil;

    if not CreatePipe({var}stdOutRead, {var}stdOutWrite, @securityAttributes, 0) then
        RaiseLastOSError;
    try
        // Set up members of the STARTUPINFO structure.
        startupInfo := Default(TStartupInfo);
        startupInfo.cb := SizeOf(startupInfo);

        // This structure specifies the STDIN and STDOUT handles for redirection.
        startupInfo.dwFlags := startupInfo.dwFlags or STARTF_USESTDHANDLES; //The hStdInput, hStdOutput, and hStdError handles will be valid.
            startupInfo.hStdInput := GetStdHandle(STD_INPUT_HANDLE); //don't forget to make it valid (zero is not valid)
            startupInfo.hStdOutput := stdOutWrite; //give the console app the writable end of the pipe
            startupInfo.hStdError := stdOutWrite; //give the console app the writable end of the pipe

        // We also want the console window to be hidden
        startupInfo.dwFlags := startupInfo.dwFlags or STARTF_USESHOWWINDOW; //The nShowWindow member member will be valid.
            startupInfo.wShowWindow := SW_HIDE; //default is that the console window is visible

        // Set up members of the PROCESS_INFORMATION structure.
        pi := Default(TProcessInformation);

        //WARNING: The Unicode version of CreateProcess can modify the contents of CommandLine.
        //Therefore CommandLine cannot point to read-only memory.
        //We can ensure it's not read-only with the RTL function UniqueString
        UniqueString({var}CommandLine);

        bRes := CreateProcess(nil, PChar(CommandLine), nil, nil, True, 0, nil, nil, startupInfo, {var}pi);
        if not bRes then
            RaiseLastOSError;

        //CreateProcess demands that we close these two populated handles when we're done with them. We're done with them.
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);

        {
            We've given the console app the writable end of the pipe during CreateProcess; we don't need it anymore.
            We do keep the handle for the *readable* end of the pipe; as we still need to read from it.
            The other reason to close the writable-end handle now is so that there's only one out-standing reference to the writeable end: held by the console app.
            When the app closes, it will close the pipe, and ReadFile will return code 109 (The pipe has been ended).
            That's how we'll know the console app is done. (no need to wait on process handles)
        }
        CloseHandle(stdOutWrite);
        stdOutWrite := 0;

        SetLength(buffer, 4096);

        //Read will return when the buffer is full, or if the pipe on the other end has been broken
        while ReadFile(stdOutRead, buffer[1], Length(buffer), {var}bytesRead, nil) do
            Result := Result + string(Copy(buffer, 1, bytesRead));

        //ReadFile will either tell us that the pipe has closed, or give us an error
        le := GetLastError;
        if le <> ERROR_BROKEN_PIPE then //"The pipe has been ended."
            RaiseLastOSError(le);
    finally
        CloseHandle(stdOutRead);
        if stdOutWrite <> 0 then
            CloseHandle(stdOutWrite);
    end;
end;

【讨论】:

  • @SrinivasPaladugu 当ReadFile立即返回false(即不等待,不返回任何数据)时,GetLastError返回什么错误码?跨度>
  • 谢谢!您的彻底回答让我理解并修复了我的代码。顺便说一句,您是否碰巧知道一本好书解释了诸如管道使用不当引起的死锁之类的概念?
  • @GrzegorzWolszczak 我不记得是怎么想出来的。我有这样做的需要,所以进行了大量的研究。当我发现微妙的陷阱时,我知道我需要在那里获取正确的代码。所以我只是在 StackOverflow 上寻找一个关于获取控制台输出的问题,并用这个问题作为我转储正确答案的地方。
【解决方案2】:

Ian Boyd 的回答是这样的:启动子进程后:确保关闭不再需要的管道末端。

我制作了另一个版本的 CreatePipe + CreateProcess 解决方案,我希望它更清楚:

int main()
{
    BOOL ok = TRUE;
    HANDLE hStdInPipeRead = NULL;
    HANDLE hStdInPipeWrite = NULL;
    HANDLE hStdOutPipeRead = NULL;
    HANDLE hStdOutPipeWrite = NULL;

    // Create two pipes.
    SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
    ok = CreatePipe(&hStdInPipeRead, &hStdInPipeWrite, &sa, 0);
    if (ok == FALSE) return -1;
    ok = CreatePipe(&hStdOutPipeRead, &hStdOutPipeWrite, &sa, 0);
    if (ok == FALSE) return -1;

    // Create the process.
    STARTUPINFO si = { };
    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdError = hStdOutPipeWrite;
    si.hStdOutput = hStdOutPipeWrite;
    si.hStdInput = hStdInPipeRead;
    PROCESS_INFORMATION pi = { };
    LPCWSTR lpApplicationName = L"C:\\Windows\\System32\\cmd.exe";
    LPWSTR lpCommandLine = (LPWSTR)L"C:\\Windows\\System32\\cmd.exe /c dir";
    LPSECURITY_ATTRIBUTES lpProcessAttributes = NULL;
    LPSECURITY_ATTRIBUTES lpThreadAttribute = NULL;
    BOOL bInheritHandles = TRUE;
    DWORD dwCreationFlags = 0;
    LPVOID lpEnvironment = NULL;
    LPCWSTR lpCurrentDirectory = NULL;
    ok = CreateProcess(
        lpApplicationName,
        lpCommandLine,
        lpProcessAttributes,
        lpThreadAttribute,
        bInheritHandles,
        dwCreationFlags,
        lpEnvironment,
        lpCurrentDirectory,
        &si,
        &pi);
    if (ok == FALSE) return -1;

    // Close pipes we do not need.
    CloseHandle(hStdOutPipeWrite);
    CloseHandle(hStdInPipeRead);

    // The main loop for reading output from the DIR command.
    char buf[1024 + 1] = { };
    DWORD dwRead = 0;
    DWORD dwAvail = 0;
    ok = ReadFile(hStdOutPipeRead, buf, 1024, &dwRead, NULL);
    while (ok == TRUE)
    {
        buf[dwRead] = '\0';
        OutputDebugStringA(buf);
        puts(buf);
        ok = ReadFile(hStdOutPipeRead, buf, 1024, &dwRead, NULL);
    }

    // Clean up and exit.
    CloseHandle(hStdOutPipeRead);
    CloseHandle(hStdInPipeWrite);
    DWORD dwExitCode = 0;
    GetExitCodeProcess(pi.hProcess, &dwExitCode);
    return dwExitCode;
}

一些注意事项:

  • StdIn 的管道并不是真正需要的:
    • 这是因为DIR 命令不需要用户输入(但是,我把它留在了代码中,因为它是运行其他命令的好模板)
    • hStdInPipeRead & hStdInPipeWrite 相关的所有内容都可以省略
    • 设置si.hStdInput可以省略
  • 将硬编码的L"C:\\Windows\\System32\\cmd.exe" 替换为读取COMSPEC 环境变量。
  • 如果我们希望针对非 UNICODE 进行编译,请将 LPWSTR 替换为 LPTSTR。
  • cmd.exe /k DIR 替换为cmd.exe /c DIR,因为当DIR 命令完成时,我们真的不希望cmd.exe 继续存在。

【讨论】:

  • lpCommandLine 不应指向文字字符串,因为 CreateProcessW 函数可能会修改它。
【解决方案3】:

我也有同样的情况。就我而言,来自 Lib,需要执行内部 exe 并读取输出。以下工作没有任何问题。

  void executeCMDInNewProcessAndReadOutput(LPSTR lpCommandLine)
    {
        STARTUPINFO si;
        SECURITY_ATTRIBUTES sa;
        PROCESS_INFORMATION pi;
        HANDLE g_hChildStd_IN_Rd, g_hChildStd_OUT_Wr, g_hChildStd_OUT_Rd, g_hChildStd_IN_Wr;  //pipe handles
        char buf[1024];           //i/o buffer

        sa.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa.bInheritHandle = TRUE;
        sa.lpSecurityDescriptor = NULL;

        if (CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &sa, 0))   //create stdin pipe
        {
            if (CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &sa, 0))  //create stdout pipe
            {

                //set startupinfo for the spawned process
                /*The dwFlags member tells CreateProcess how to make the process.
                STARTF_USESTDHANDLES: validates the hStd* members.
                STARTF_USESHOWWINDOW: validates the wShowWindow member*/
                GetStartupInfo(&si);

                si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
                si.wShowWindow = SW_HIDE;
                //set the new handles for the child process
                si.hStdOutput = g_hChildStd_OUT_Wr;
                si.hStdError = g_hChildStd_OUT_Wr;
                si.hStdInput = g_hChildStd_IN_Rd;

                //spawn the child process
                if (CreateProcess(NULL, lpCommandLine, NULL, NULL, TRUE, CREATE_NEW_CONSOLE,
                    NULL, NULL, &si, &pi))
                {
                    unsigned long bread;   //bytes read
                    unsigned long avail;   //bytes available
                    memset(buf, 0, sizeof(buf));

                    for (;;)
                    {
                        PeekNamedPipe(g_hChildStd_OUT_Rd, buf, 1023, &bread, &avail, NULL);
                        //check to see if there is any data to read from stdout
                        if (bread != 0)
                        {
                            if (ReadFile(g_hChildStd_OUT_Rd, buf, 1023, &bread, NULL))
                            {
                                break;
                            }
                        }
                    }

                    //clean up all handles
                    CloseHandle(pi.hThread);
                    CloseHandle(pi.hProcess);
                    CloseHandle(g_hChildStd_IN_Rd);
                    CloseHandle(g_hChildStd_OUT_Wr);
                    CloseHandle(g_hChildStd_OUT_Rd);
                    CloseHandle(g_hChildStd_IN_Wr);
                }
                else
                {
                    CloseHandle(g_hChildStd_IN_Rd);
                    CloseHandle(g_hChildStd_OUT_Wr);
                    CloseHandle(g_hChildStd_OUT_Rd);
                    CloseHandle(g_hChildStd_IN_Wr);
                }
            }
            else
            {
                CloseHandle(g_hChildStd_IN_Rd);
                CloseHandle(g_hChildStd_IN_Wr);
            }
        }
    }

【讨论】:

    【解决方案4】:

    这是一个执行您正在寻找的线程的示例(取自一个更大的程序)。它为它创建的进程创建 stdout 和 stderr 管道,然后进入循环读取这些管道,直到程序完成。

    DWORD WINAPI ThreadProc(LPVOID lpParameter)
       {
    #define EVENT_NAME "Global\\RunnerEvt"
    
       HANDLE hev;
       SECURITY_ATTRIBUTES psa;
       InitSAPtr(&psa);
       DWORD waitRc;
       DWORD bytesRead;
       int manual_triggered = 1;
    
       hev = CreateEvent(&psa, FALSE, FALSE, EVENT_NAME);
    
       // Create pipes we'll read
    
          for(;;)
          {
    
          if (manual_triggered)
             {
             waitRc = WAIT_OBJECT_0;
             manual_triggered = 0;
             }
          else
             {
             waitRc = WaitForSingleObject(hev, 500);
             }
    
          if (waitRc == WAIT_OBJECT_0)
             {
             `logprint`f(LOG_DBG, "Received command to run process\n");
    
             CreateChildOutFile();
    
             stdOutEvt = CreateEvent(&psa, TRUE, FALSE, 0);
             stdOutOvl.hEvent = stdOutEvt;
    
             stdErrEvt = CreateEvent(&psa, TRUE, FALSE, 0);
             stdErrOvl.hEvent = stdErrEvt;
    
             gStdOutReadHand =  CreateNamedPipe(STD_OUT_PIPE_NAME, PIPE_ACCESS_DUPLEX + FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE + PIPE_READMODE_BYTE,
                PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &psa);
             if (gStdOutReadHand == INVALID_HANDLE_VALUE)
                {
                log(LOG_DBG, "Error %d on create STDOUT pipe\n", GetLastError());
                }
    
             gStdErrReadHand =  CreateNamedPipe(STD_ERR_PIPE_NAME, PIPE_ACCESS_DUPLEX + FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE + PIPE_READMODE_BYTE,
                PIPE_UNLIMITED_INSTANCES, 4096, 4096, 0, &psa);
             if (gStdErrReadHand == INVALID_HANDLE_VALUE)
                {
                log(LOG_DBG, "Error %d on create STDERR pipe\n", GetLastError());
                }
    
             runProcess();
    
             log(LOG_DBG, "After runProcess, new PID is %d/%x\n", piProcInfo.dwProcessId, piProcInfo.dwProcessId);
    
             if (piProcInfo.dwProcessId == 0)
                {
                log(LOG_DBG, "runProcess failed, closing child STDIN/STDERR\n");
                closeChildPipes();
    
    #define FAIL_MSG "Child process failed to start\n"
                writeChildOutFile(FAIL_MSG, strlen(FAIL_MSG) );
    
                CloseHandle(hChildOut);
                }
             else
                {
                log(LOG_DBG, "Child process created, setting up for redir/restart/termination\n");
    
                issueRead(gStdOutReadHand, &stdOutOvl, stdOutBuff, &stdOutBytesAvail);
                //log(LOG_DBG, "After read set on STDOUT\n");
    
                issueRead(gStdErrReadHand, &stdErrOvl, stdErrBuff, &stdErrBytesAvail);
                //log(LOG_DBG, "After read set on STDERR\n");
    
                HANDLE harr[4];
    
                for(;;)
                   {
                   harr[0] = hev;
                   harr[1] = piProcInfo.hProcess;
                   harr[2] = stdOutEvt;
                   harr[3] = stdErrEvt;
    
                   DWORD waitRc2 = WaitForMultipleObjects(4, harr, FALSE, 500);
    
                   #if 0
                   if (waitRc2 == -1)
                      {
                      log(LOG_DBG, "Wait error %d\n", GetLastError());
                      Sleep(500);
                      }
    
                   log(LOG_DBG, "waitRc2 %d\n", waitRc2);
                   #endif
    
    
                   if ((waitRc2 - WAIT_OBJECT_0) == 0)
                      {
                      log(LOG_DBG, "Woke up because another trigger command was received\n");
                      #define NEW_CMD_MSG "Child process is being terminated because new trigger received\n"
    
                      writeChildOutFile(NEW_CMD_MSG, strlen(NEW_CMD_MSG));
    
                      terminateChild();
                      CloseHandle(hChildOut);
                      manual_triggered = 1;
                      break;
                      }
                   else if ((waitRc2 - WAIT_OBJECT_0) == 1)
                      {
                      //log(LOG_DBG, "Woke up because child has terminated\n");
                      closeChildPipes();
                      #define NORM_MSG "Normal child process termination\n"
                      writeChildOutFile(NORM_MSG, strlen(NORM_MSG));
                      CloseHandle(hChildOut);
                      break;
                      }
                   else if ((waitRc2 - WAIT_OBJECT_0) == 2)
                      {
                      //log(LOG_DBG, "Woke up because child has stdout\n");
                      if (GetOverlappedResult(gStdOutReadHand, &stdOutOvl, &bytesRead, TRUE))
                         {
                         writeChildOutFile(stdOutBuff, bytesRead);
                         ResetEvent(stdOutEvt);
                         issueRead(gStdOutReadHand, &stdOutOvl, stdOutBuff, &stdOutBytesAvail);
                         }
    
                      }
                   else if ((waitRc2 - WAIT_OBJECT_0) == 3)
                      {
                      //log(LOG_DBG, "Woke up because child has stderr\n");
    
                      if (GetOverlappedResult(gStdErrReadHand, &stdErrOvl, &bytesRead, TRUE))
                         {
                         writeChildOutFile(stdErrBuff, bytesRead);
                         ResetEvent(stdErrEvt);
                         issueRead(gStdErrReadHand, &stdErrOvl, stdErrBuff, &stdErrBytesAvail);
                         }
                      }
                   else
                      {
                      if (gShuttingDown)
                         {
                         log(LOG_DBG, "Woke with active child and service is terminating\n");
    
    #define SHUTDOWN_MSG "Child process is being terminated because the service is shutting down\n"
    
                         writeChildOutFile(SHUTDOWN_MSG, strlen(SHUTDOWN_MSG));
                         terminateChild();
                         CloseHandle(hChildOut);
                         break;
                         }
                      }
    
                   if (gShuttingDown)
                      {
                      break;
                      }
    
                   }
                }
             }
          else if (gShuttingDown)
             {
             break;
             }
    
          CloseHandle(gStdOutReadHand);
          CloseHandle(gStdErrReadHand);
    
          }
    
       return 0;
       }
    
    void writeChildOutFile(char *msg, int len)
       {
       DWORD bytesWritten;
       WriteFile(hChildOut, msg, len, &bytesWritten, 0);
       }
    
    
    void terminateChild(void)
       {
       if (piProcInfo.dwProcessId != 0)
          {
          TerminateProcess(piProcInfo.hProcess, -1);
          CloseHandle(piProcInfo.hThread);
          CloseHandle(piProcInfo.hProcess);
          closeChildPipes();
          }
       }
    
    void closeChildPipes(void)
       {
       CloseHandle(g_hChildStd_OUT_Wr);
       CloseHandle(g_hChildStd_ERR_Wr);
       }
    
    void runProcess(void)
       {
       SECURITY_ATTRIBUTES saAttr;
    
       // Set the bInheritHandle flag so pipe handles are inherited.
       saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
       saAttr.bInheritHandle = TRUE;
       saAttr.lpSecurityDescriptor = NULL;
    
       // Create a pipe for the child process's STDOUT.
       TCHAR szCmdline[]=TEXT("cmd.exe /C C:\\temp\\RunnerService.bat");
       STARTUPINFO siStartInfo;
       BOOL bSuccess = FALSE;
    
    // Set up members of the PROCESS_INFORMATION structure.
    
       ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
    
       g_hChildStd_OUT_Wr = CreateFile (STD_OUT_PIPE_NAME,
                    FILE_WRITE_DATA,
                    0,
                    &saAttr,
                    OPEN_EXISTING,
                    0,
                    NULL);
    
       if (g_hChildStd_OUT_Wr == INVALID_HANDLE_VALUE)
          {
          log(LOG_DBG, "Error creating child proc stdout file %d\n", GetLastError());
          }
    
    
       g_hChildStd_ERR_Wr = CreateFile (STD_ERR_PIPE_NAME,
                    FILE_WRITE_DATA,
                    0,
                    &saAttr,
                    OPEN_EXISTING,
                    0,
                    NULL);
    
       if (g_hChildStd_ERR_Wr == INVALID_HANDLE_VALUE)
          {
          log(LOG_DBG, "Error creating child proc stderr file %d\n", GetLastError());
          }
    
    
    // Set up members of the STARTUPINFO structure.
    // This structure specifies the STDIN and STDOUT handles for redirection.
    
       ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
       siStartInfo.cb = sizeof(STARTUPINFO);
       siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
       siStartInfo.hStdError = g_hChildStd_ERR_Wr;
       siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
    
    // Create the child process.
    
       bSuccess = CreateProcess(NULL,
          szCmdline,     // command line
          NULL,          // process security attributes
          NULL,          // primary thread security attributes
          TRUE,          // handles are inherited
          0,             // creation flags
          NULL,          // use parent's environment
          NULL,          // use parent's current directory
          &siStartInfo,  // STARTUPINFO pointer
          &piProcInfo);  // receives PROCESS_INFORMATION
    
       }
    
    
    void CreateChildOutFile(void)
       {
       SYSTEMTIME st;
       SECURITY_ATTRIBUTES sa;
       char fName[_MAX_PATH];
    
       InitSAPtr(&sa);
    
       GetLocalTime(&st);
    
       sprintf(fName, "C:\\TEMP\\runsvcchild_%02d_%02d_%02d_%04d.out", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    
       hChildOut = CreateFile(fName, GENERIC_WRITE, FILE_SHARE_READ, &sa,  CREATE_ALWAYS,  FILE_ATTRIBUTE_NORMAL, 0);
       }
    
    void issueRead(HANDLE hFile, OVERLAPPED *overLapped, char *buf, DWORD *dwRead)
       {
       //log(LOG_DBG, "Start of issueRead, hfile %08x, ovl is %08x\n", hFile, overLapped);
       BOOL brc = ReadFile(hFile, buf, 4096, dwRead, overLapped);
       if (!brc)
          {
          DWORD dwle = GetLastError();
          if (dwle != ERROR_IO_PENDING)
             {
             log(LOG_DBG, "Error %d on ReadFile\n", dwle);
             }
          }
       else
          {
          // log(LOG_DBG, "Read issued\n");
          }
       }  
    

    【讨论】:

      【解决方案5】:

      我认为你做的一切都是对的。但是 cmd.exe 在启动后不会打印任何数据或打印很少的数据,并且您的 ReadFile 会阻塞。如果你移动你的周期

      while (true) {
          bStatus = ReadFile(g_hChildStd_OUT_Rd, aBuf, sizeof(aBuf), &dwRead, NULL);
          if (!bStatus || dwRead == 0) {
              break;
          }
          aBuf[dwRead] = '\0';
          printf("%s\n", aBuf);
      }
      

      进入后台线程并运行其他循环,它将读取您的输入并将其发送到 cmd.exe,我想您可以看到任何效果。 您可以使读取缓冲区更小(例如 16 个字节)。

      【讨论】:

        【解决方案6】:

        我尝试了 Stephen Quan 的答案并得到了一个段错误。也许有更多经验的人可能知道为什么会这样。无论如何,这应该是他试图做的一个更正确的例子:

        #include <windows.h>
        #include <cstddef>
        #include <string>
        #include <vector>
        #include <cwchar>
        
        using std::string;
        using std::wstring;
        using std::vector;
        using std::size_t;
        
        static inline wstring widen(string str) {
          size_t wchar_count = str.size() + 1;
          vector<wchar_t> buf(wchar_count);
          return wstring{ buf.data(), (size_t)MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, buf.data(), (int)wchar_count) };
        }
        
        static inline string narrow(wstring wstr) {
          int nbytes = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);
          vector<char> buf(nbytes);
          return string{ buf.data(), (size_t)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), buf.data(), nbytes, NULL, NULL) };
        }
        
        string evaluate_shell(string command) {
          string output;
          wstring wstr_command = widen(command);
          wchar_t cwstr_command[32768];
          wcsncpy(cwstr_command, wstr_command.c_str(), 32768);
          BOOL ok = TRUE;
          HANDLE hStdInPipeRead = NULL;
          HANDLE hStdInPipeWrite = NULL;
          HANDLE hStdOutPipeRead = NULL;
          HANDLE hStdOutPipeWrite = NULL;
          SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
          ok = CreatePipe(&hStdInPipeRead, &hStdInPipeWrite, &sa, 0);
          if (ok == FALSE) return "";
          ok = CreatePipe(&hStdOutPipeRead, &hStdOutPipeWrite, &sa, 0);
          if (ok == FALSE) return "";
          STARTUPINFOW si = { };
          si.cb = sizeof(STARTUPINFOW);
          si.dwFlags = STARTF_USESTDHANDLES;
          si.hStdError = hStdOutPipeWrite;
          si.hStdOutput = hStdOutPipeWrite;
          si.hStdInput = hStdInPipeRead;
          PROCESS_INFORMATION pi = { };
          if (CreateProcessW(NULL, cwstr_command, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
            while (WaitForSingleObject(pi.hProcess, 5) == WAIT_TIMEOUT) {
              MSG msg;
              if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
              }
            }
            CloseHandle(hStdOutPipeWrite);
            CloseHandle(hStdInPipeRead);
            char buffer[4096] = { };
            DWORD dwRead = 0;
            ok = ReadFile(hStdOutPipeRead, buffer, 4095, &dwRead, NULL);
            while (ok == TRUE) {
              buffer[dwRead] = 0;
              ok = ReadFile(hStdOutPipeRead, buffer, 4095, &dwRead, NULL);
            }
            CloseHandle(hStdOutPipeRead);
            CloseHandle(hStdInPipeWrite);
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
            output = narrow(widen(buffer));
            while (output.back() == '\r' || output.back() == '\n')
              output.pop_back();
          }
          return output;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-12-13
          • 1970-01-01
          • 2013-12-26
          • 1970-01-01
          • 2017-01-24
          • 2014-01-28
          • 1970-01-01
          • 2011-09-04
          相关资源
          最近更新 更多