5

你好,这个伟大的社区,

当使用管道将孩子的标准输出重定向到文件时,我遇到了自动转换的问题,孩子的输出是 bytes('\n') 0x0A而不是text('\n\r') 0x0D 0x0A

首先,我使用了这些示例MSDN-Creating a Child Process with Redirected Input and Outputhttp://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从父母那里对孩子的标准输出做。

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

相关问题Win32 流句柄 - 更改为二进制模式,但他可以访问孩子的代码!

编辑

正如@librik 点,最终的解决方案将不得不用 0x0A 替换每一次出现的 0x0D 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;
}
4

1 回答 1

3

Your problem is that the "stream mode" isn't part of Windows, so it isn't something you can change from outside the other program. It's part of the C and C++ system, and so it's a private part of each separate C or C++ program you run.

There's a library of functions that is combined with every program compiled in C++, called the "C++ Standard Library." The C++ Standard Library holds all the functions for streams like stdout. It's inside the other program's C++ Standard Library that the 0x0A is being converted to 0x0D 0x0A before it's written to the stream. _setmode is a function inside the C++ Standard Library which turns on and off that conversion, so when you add a call to it inside child.cpp, that tells child.cpp's C++ Standard Library to leave stdout alone. But you have no way to force the other program to call its _setmode function.

So the best thing really is the "crazy" solution you suggested:

As a solution, I am seriously considering finding every '0x0D' '0x0A' and replacing it with '0x0A'.

So long as you know that child.exe is writing in text mode, not binary mode, then every single occurrence of 0x0D 0x0A must have originally been a single 0x0A. (If the program tried to write the two bytes 0x0D 0x0A, it would come out as the three bytes 0x0D 0x0D 0x0A.) Therefore you are absolutely safe and correct to "fix" the output by converting back again.

I think the easiest approach is just to write result.out exactly like you're doing it now, but then translate 0x0D 0x0A to 0x0A at the end, creating a new file that is correct. There are little tool programs you can download that will do this sort of thing for you -- one of them is called dos2unix. That might be the easiest way, in fact -- just make the last step of your program run dos2unix < result.out.with_bad_newlines > result.out. If, for some reason, you can't do this, you could have your program change 0x0D 0x0A to 0x0A inside chBuf before you write it out, translating as you go. (But be careful when chBuf ends in 0x0D...)

(There are certain techniques that can "inject" a little bit of code into another program that's under your control on Windows. They are a little dangerous, and a lot of trouble. If you're really unhappy with the translation idea, you can look up "DLL injection.")

于 2013-08-18T02:34:12.977 回答