Win32 更改为二进制模式子的标准输出(管道)

Posted

技术标签:

【中文标题】Win32 更改为二进制模式子的标准输出(管道)【英文标题】:Win32 changing to binary mode child's Stdout (pipe) 【发布时间】:2013-08-18 00:37:56 【问题描述】:

你好,这个伟大的社区,

当使用管道将孩子的标准输出重定向到文件时,('\n') 0x0A('\n\r') 0x0D 0x0A 的自动转换存在问题,孩子的输出是字节,不是文本

首先,我使用了这些示例MSDN-Creating a Child Process with Redirected Input and Output 和http://support.microsoft.com/kb/190351),现在我有了这个基本应用程序,它创建了一个管道并将孩子的 STDOUT 重定向到一个二进制文件。所有这些都在 Visual C++ 6.0 中的 Win32 控制台应用程序中(是的,它很旧,但这是必需的)。

#define BUFSIZE 256

HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;

int _tmain(int argc, TCHAR *argv[]) 
 

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

    if ( ! CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0) ) 
        ErrorExit(TEXT("StdoutRd CreatePipe")); 

    if ( ! SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0) )
        ErrorExit(TEXT("Stdout SetHandleInformation")); 

    CreateChildProcess();

    if (!CloseHandle(g_hChildStd_OUT_Wr)) 
        ErrorExit("CloseHandle");

    ReadFromPipe(); 

    if (!CloseHandle(g_hChildStd_OUT_Rd)) 
        ErrorExit("CloseHandle");

    return 0; 
 


void CreateChildProcess()
 
    TCHAR szCmdline[]=TEXT("child.exe");
    PROCESS_INFORMATION piProcInfo; 
    STARTUPINFO siStartInfo;
    BOOL bSuccess = FALSE; 

    ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );

    ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
    siStartInfo.cb = sizeof(STARTUPINFO); 
    siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

    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 

    if ( ! bSuccess ) 
        ErrorExit(TEXT("CreateProcess"));
    else 
    
        CloseHandle(piProcInfo.hProcess);
        CloseHandle(piProcInfo.hThread);
    



void ReadFromPipe(void) 
 
    DWORD dwRead, dwWritten; 
    CHAR chBuf[BUFSIZE]; 
    BOOL bSuccess = FALSE;
    HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

    DWORD nTotalBytesRead = 0;
    fstream filePk;
    filePk.open("result.out", ios::out | ios::trunc | ios::binary);

    for (;;) 
     

        bSuccess = ReadFile( g_hChildStd_OUT_Rd, chBuf, BUFSIZE, &dwRead, NULL);
        if( ! bSuccess || dwRead == 0 ) 
            if (GetLastError() == ERROR_BROKEN_PIPE)
                break; // pipe done - normal exit path.
            else
                ErrorExit("ReadFile"); // Something bad happened.
        

        filePk.write(chBuf, dwRead);
        nTotalBytesRead += dwRead;
     
    filePk.close();

    char ibuff[24]; 
    sprintf(ibuff,"%d bytes." , (int)nTotalBytesRead);
    ::MessageBox(NULL, ibuff, "", 0);
 

在这个 dummy child.cpp 中,您会注意到,如果我将 STDOUT 设置为二进制模式,一切正常(我只得到 0x0A 0x0A!),但我真正的孩子是 EXE 而我没有无法访问该代码

int main(int argc, char* argv[])

    _setmode( _fileno( stdout ), _O_BINARY );
    printf("\n");

    unsigned char buffer[] = '\n';
    fwrite(buffer, sizeof(unsigned char), sizeof(buffer), stdout);
    return 0;

所以,在搜索了大约 2 天后,考虑到我有基本的 C++ 知识,我问:考虑到我没有,有没有办法可以从父母那里对孩子的标准输出做_setmode t 有权访问孩子的代码

作为一种解决方案,我正在认真考虑找到每个'0x0D' '0x0A' 并将其替换为'0x0A'。我真的为这个问题发疯了......所以如果有人可以帮助我,我将非常感激。

相关问题:Win32 Stream Handles - Changing To Binary Mode 但他可以访问孩子的代码!

编辑

正如@librik 的观点,最终的解决方案必须将每一次出现的 0x0D 0x0A 替换为 0x0A。为此,文件内容必须在内存中。存在某些问题,但我可以忍受(分配的内存过多)。我希望这会有所帮助:

void ReadFromPipe(void) 
 
    DWORD dwRead, dwWritten; 
    CHAR *chBuf = NULL, *chBufTmp = NULL; 
    BOOL bSuccess = FALSE;
    HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

    DWORD nTotalBytesRead = 0;
    fstream filePk;
    filePk.open("result.out", ios::out | ios::trunc | ios::binary);

    int nIter = 0;
    for (;;) 
    
        if(chBuf == NULL) 
            if((chBuf = (CHAR*)malloc(BUFSIZE*sizeof(CHAR))) == NULL) 
                ErrorExit("Malloc");
            
         else 
            chBufTmp = chBuf;   // save pointer in case realloc fails
            if((chBuf = (CHAR*)realloc(chBuf, (nIter+1)*(BUFSIZE*sizeof(CHAR)))) == NULL) 
                free(chBufTmp); // free original block
                ErrorExit("Realloc");
            
        

        CHAR* chBufNew = chBuf+nTotalBytesRead;
        bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBufNew, BUFSIZE, &dwRead, NULL);
        if( ! bSuccess || dwRead == 0 ) 
            if (GetLastError() == ERROR_BROKEN_PIPE) 
                break; // pipe done - normal exit path.
             else 
                ErrorExit("ReadFile"); // Something bad happened.
            
        

        nTotalBytesRead += dwRead;
        nIter ++;
     

    // 0xD 0xA -> 0xA
    nTotalBytesRead = ClearBuffer(chBuf, nTotalBytesRead);

    filePk.write(chBuf, nTotalBytesRead);
    filePk.close();
    free(chBuf);

    char ibuff[24]; 
    sprintf(ibuff,"%d bytes." , (int)nTotalBytesRead);
    ::MessageBox(NULL, ibuff, "", 0);
 

int ClearBuffer(char *buffer, int bufferlength) 
    // lmiguelhm-es requerido que TODO el buffer esté en memoria 
    int chdel = 0;
    for (int i = 0; (i+chdel) < bufferlength; i++) 
        char firstChar = buffer[i+chdel];
        buffer[i] = firstChar;
        if (firstChar == 0x0D) 
            if ((i+chdel+1) < bufferlength) 
                char secondChar = buffer[i+chdel+1];
                if (secondChar == 0x0A) 
                    buffer[i] = secondChar;
                    chdel++;
                
            
        
    
    return bufferlength - chdel;

【问题讨论】:

【参考方案1】:

您的问题是“流模式”不是 Windows 的一部分,因此您无法从其他程序外部更改它。它是 C 和 C++ 系统的一部分,因此它是您运行的每个单独的 C 或 C++ 程序的私有部分。

有一个函数库,它与用 C++ 编译的每个程序相结合,称为“C++ 标准库”。 C++ 标准库包含stdout 等流的所有函数。在另一个程序的 C++ 标准库中,0x0A 在写入流之前被转换为 0x0D 0x0A。 _setmode 是 C++ 标准库中的一个函数,它打开和关闭该转换,所以当你在 child.cpp 中添加对它的调用时,它会告诉 child.cpp 的 C++ 标准库不要管 stdout。但是你没有办法强制其他程序调用它的_setmode函数。

所以最好的确实是您建议的“疯狂”解决方案:

作为一种解决方案,我正在认真考虑找到每个 '0x0D' '0x0A' 并将其替换为“0x0A”。

只要您知道 child.exe 是以文本模式而不是二进制模式编写的,那么每次出现的 0x0D 0x0A 必须最初是单个 0x0A . (如果程序试图写入两个字节 0x0D 0x0A,它会输出三个字节 0x0D 0x0D 0x0A。)因此,通过再次转换回来“修复”输出是绝对安全和正确的。

我认为最简单的方法就是像现在一样写result.out,但最后将 0x0D 0x0A 转换为 0x0A,创建一个正确的新文件。你可以下载一些小工具程序来为你做这种事情——其中一个叫做dos2unix。事实上,这可能是最简单的方法——只需让程序的最后一步运行dos2unix &lt; result.out.with_bad_newlines &gt; result.out。如果由于某种原因你不能这样做,你可以在你写出来之前让你的程序在 chBuf 中将 0x0D 0x0A 更改为 0x0A,边做边翻译。 (但当 chBuf ends 在 0x0D 时要小心...)

(有些技术可以将一点代码“注入”到另一个您在 Windows 上控制的程序中。它们有点危险,也很麻烦。如果您对翻译想法真的不满意,你可以查一下“DLL注入”)

【讨论】:

谢谢!还有一件事,我的孩子正在用二进制模式写作,这个解决方案也适用于二进制模式吗? 我以为你说孩子在文本模式下有标准输出。如果孩子的标准输出是二进制模式,那么我不明白你为什么会看到 0x0D 0x0A 。你的问题现在很混乱,似乎自相矛盾。如果流是文本模式流,则不能以二进制模式写入,并且如果您没有调用 _setmode(stdout, O_BINARY),则 stdout 流是文本模式流。 糟糕,抱歉。我应该说:孩子写的是字节而不是文本。我添加了最终解决方案。再次感谢。

以上是关于Win32 更改为二进制模式子的标准输出(管道)的主要内容,如果未能解决你的问题,请参考以下文章

Win32 应用程序获取像素以更改为全蓝色、红色或绿色

Windows IPC:我可以通过匿名管道发送二进制数据吗?

在重定向的stdout管道上禁用缓冲(Win32 API,C ++)

Linux 第三天 重定负管道符环境变量

datepickerdialog 隐藏日历并将我的日期选择器视图更改为标准模式

在win32中重定向标准输出不会重定向标准输出