通过 child 将 CreateProcess 的 grandchild 输出传递给祖父母时的奇怪行为

Weird behaviour when passing grandchild output of CreateProcess to the grandparent through the child

我正在开发一个应用程序 A,它调用 CreateProcess 到 运行 一个 child 进程 B,后者又调用 createProcess 到 运行 一个child 处理 C。C 的输出应由 B 传递,然后由 A 读取。当我 运行 B 自己(在 A 之外)时,它工作正常,C 的输出显示在控制台。但是当我 运行 A 时,它可以看到 B 的所有输出 除了从 C 传递的内容。 Apparently,我正在为 grandchildren 或类似的东西打一个特例。

为了进行实验,我创建了一个简单的示例 test.c,它以数字作为参数。它输出与数字对应的字母(a 表示 1,等等)。然后它使用 CreateProcess 递归地启动同一个程序,但减少参数。它以 <N: OUTPUT> 格式传递来自 child 的所有输出,其中 N 是数字参数。

我希望看到以下结果:

> .\test.exe 1
a
> .\test.exe 2
b<2: a>
> .\test.exe 3
c<3: b><3: <2: a>>

我意识到由于缓冲,最后的输出也可能类似于 c<3: b<2: a>> ---重要的是输出 a 嵌套在 <2:> 块中并且<2:> 块嵌套在 <3:> 块中:这表明 grandchild 的输出首先由 child 接收,然后传递给 parent.

我的程序对于参数 12 的行为是正确的,但是对于 3 我始终得到这个输出:

c<3: a><3: b<2: >>

这对我来说完全没有意义。就好像grandchild的输出直接被最顶层的进程接收了一样,因为我们得到了<3: a>。我也不知道如何解释 <3: b<2: >>

如何修复此示例,以便 child 首先看到 grandchild 的输出,然后传递给 parent?

#include <stdio.h> 
#include <windows.h> 

static counter; /* the digit argument */
static HANDLE stdout_read;

static void error (char *s)
{
    fprintf (stderr,"error: %s\n",s);
    exit (1);
}

/* Thread function to pass through output */
static DWORD WINAPI copy_out_func (LPVOID unused)
{
    CHAR buffer[512];

    for (;;) {
        DWORD n_bytes;

        if (!ReadFile (stdout_read,buffer,sizeof (buffer),&n_bytes,NULL) || n_bytes==0)
            break;
        printf ("<%d: ",counter);
        WriteFile (GetStdHandle (STD_OUTPUT_HANDLE),buffer,n_bytes,&n_bytes,NULL);
        printf (">");
    }

    return 1;
}

int main(int argc, TCHAR *argv[]) 
{
    SECURITY_ATTRIBUTES sa;
    HANDLE stdout_write,copy_out_thread;
    char cmd[64]=".\test.exe";
    PROCESS_INFORMATION pi; 
    STARTUPINFO si;
    BOOL bSuccess = FALSE; 

    if (argc != 2)
        error ("Usage: .\test.exe N");

    counter = argv[1][0]-'0';

    if (counter <= 0)
        return 0; /* end recursion */

    printf ("%c",'a'+counter-1);

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

    if (!CreatePipe (&stdout_read,&stdout_write,&sa,0))
        error ("CreatePipe");
    if (!SetHandleInformation (stdout_read,HANDLE_FLAG_INHERIT,0))
        error ("SetHandleInformation");
 
    ZeroMemory (&pi,sizeof (PROCESS_INFORMATION));
 
    ZeroMemory (&si,sizeof (STARTUPINFO));
    si.cb = sizeof (STARTUPINFO); 
    si.hStdOutput = stdout_write;
    si.hStdError = stdout_write;
    si.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
    si.dwFlags |= STARTF_USESTDHANDLES;

    cmd[10]=' ';
    cmd[11]='0' + (counter-1);
    cmd[12]='[=13=]';
     
    if (!CreateProcess (NULL,cmd,NULL,NULL,TRUE,0,NULL,NULL,&si,&pi))
        error ("CreateProcess");

    CloseHandle (stdout_write);
    copy_out_thread = CreateThread (NULL,0,copy_out_func,NULL,0,NULL);

    CloseHandle (pi.hThread);

    WaitForSingleObject (pi.hProcess,INFINITE);
    WaitForSingleObject (copy_out_thread,INFINITE);

    return 0;
}

我认为这可能是因为 grandparent 的某些句柄被 ​​grandchild 错误地继承了。因此,我使用 STARTUPINFOEXLPPROC_THREAD_ATTRIBUTE_LIST 创建了一个稍长的示例,以指示仅应继承两个流。该程序具有相同的错误行为:

#include <stdio.h> 
#include <windows.h> 

static counter;
static HANDLE stdout_read;

static void error (char *s)
{
    fprintf (stderr,"error: %s (%d)\n",s,GetLastError());
    exit (1);
}

static DWORD WINAPI copy_out_func (LPVOID unused)
{
    CHAR buffer[512];

    for (;;) {
        DWORD n_bytes;

        if (!ReadFile (stdout_read,buffer,sizeof (buffer),&n_bytes,NULL) || n_bytes==0)
            break;
        printf ("<%d: ",counter);
        WriteFile (GetStdHandle (STD_OUTPUT_HANDLE),buffer,n_bytes,&n_bytes,NULL);
        printf (">");
    }

    return 1;
}

int main(int argc, TCHAR *argv[]) 
{
    SECURITY_ATTRIBUTES sa;
    HANDLE stdout_write,copy_out_thread;
    char cmd[64]=".\test.exe";
    PROCESS_INFORMATION pi; 
    SIZE_T size;
    LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
    HANDLE handlesToInherit[3];
    STARTUPINFOEX si;

    if (argc != 2)
        error ("Usage: .\test.exe N");

    counter = argv[1][0]-'0';

    if (counter <= 0)
        return 0;

    printf ("%c",'a'+counter-1);

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

    if (!CreatePipe (&stdout_read,&stdout_write,&sa,0))
        error ("CreatePipe");
    if (!SetHandleInformation (stdout_read,HANDLE_FLAG_INHERIT,0))
        error ("SetHandleInformation");
 
    ZeroMemory (&pi,sizeof (PROCESS_INFORMATION));

    handlesToInherit[0] = GetStdHandle (STD_INPUT_HANDLE);
    handlesToInherit[1] = stdout_write;
    handlesToInherit[2] = NULL;
    InitializeProcThreadAttributeList (NULL,1,0,&size);
    lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)(HeapAlloc (GetProcessHeap(),0,size));
    if (lpAttributeList == NULL)
        error ("HeapAlloc");
    if (!InitializeProcThreadAttributeList (lpAttributeList,1,0,&size))
        error ("InitializeProcThreadAttributeList");
    if (!UpdateProcThreadAttribute (lpAttributeList,0,PROC_THREAD_ATTRIBUTE_HANDLE_LIST,handlesToInherit,2*sizeof (HANDLE),NULL,NULL))
        error ("UpdateProcThreadAttribute");
 
    ZeroMemory (&si,sizeof (STARTUPINFOEX));
    si.StartupInfo.cb = sizeof (STARTUPINFOEX);
    si.StartupInfo.hStdOutput = stdout_write;
    si.StartupInfo.hStdError = stdout_write;
    si.StartupInfo.hStdInput = GetStdHandle (STD_INPUT_HANDLE);
    si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
    si.lpAttributeList = lpAttributeList;

    cmd[10]=' ';
    cmd[11]='0' + (counter-1);
    cmd[12]='[=14=]';
     
    if (!CreateProcess (NULL,cmd,NULL,NULL,TRUE,EXTENDED_STARTUPINFO_PRESENT,NULL,NULL,&si.StartupInfo,&pi))
        error ("CreateProcess");

    DeleteProcThreadAttributeList (lpAttributeList);
    HeapFree (GetProcessHeap(),0,lpAttributeList);

    CloseHandle (stdout_write);
    copy_out_thread = CreateThread (NULL,0,copy_out_func,NULL,0,NULL);

    CloseHandle (pi.hThread);

    WaitForSingleObject (pi.hProcess,INFINITE);
    WaitForSingleObject (copy_out_thread,INFINITE);

    return 0;
}

原来这与混合 WriteFileprintf 有关。据我了解,一旦缓冲区已满(或者在本例中,当程序退出时),用 printf 打印的文本将被缓冲并传递给 WriteFile 。因此,如果你 printf A 在你 WriteFile B 之前,A 可能实际上出现 B.

之后

因此,解决方案是始终使用 printf(和朋友)或 WriteFile,或者在切换到 WinAPI 之前插入 fflush