您好,这个伟大的社区,

使用管道将子级的stdout重定向到文件时,我无法自动将('\n') 0x0A转换为('\n\r') 0x0D 0x0A,子级的输出是字节而不是文本。

首先,我使用了这些示例MSDN-Creating a Child Process with Redirected Input and Outputhttp://support.microsoft.com/kb/190351),现在有了这个基本应用程序,它创建了一个管道并将 child 的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);
}

在这个虚拟的child.cpp文件中,您会注意到,如果我将STDOUT设置为二进制模式,则一切正常(我得到的只是0x0A 0x0A!),但是我真正的 child 是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++知识后,我问:有没有一种方法可以考虑到我无法访问 child 的代码,所以可以对 parent 的 child 的stdout进行_setmode的处理。

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



编辑

就像@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;
}

最佳答案

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

有一个函数库与每个用C++编译的程序结合在一起,称为“C++标准库”。 C++标准库包含stdout之类的流的所有功能。在另一个程序的C++标准库中,将0x0A写入流之前,已将其转换为0x0D 0x0A。 _setmode是C++标准库中的一个函数,用于打开和关闭该转换,因此,当您在child.cpp中添加对它的调用时,它将告诉child.cpp的C++标准库不包括stdout。但是您无法强制其他程序调用其_setmode函数。

因此,最好的事情实际上是您建议的“疯狂”解决方案:



只要您知道child.exe是在文本模式下而不是二进制模式下编写的,那么每次出现的0x0D 0x0A最初都必须是一个0x0A。 (如果程序尝试将两个字节0x0D 0x0A写入,则会以三个字节0x0D 0x0D 0x0A的形式出现。)因此,绝对安全且正确地通过再次转换来“固定”输出是正确的。

我认为最简单的方法就是完全像现在一样编写result.out,但是最后将0x0D 0x0A转换为0x0A,从而创建一个正确的新文件。您可以下载很少的工具程序来为您做这种事情-其中一个称为dos2unix。实际上,这可能是最简单的方法-仅使程序的最后一步运行dos2unix < result.out.with_bad_newlines > result.out即可。如果由于某种原因而无法执行此操作,则可以在写入之前将程序在chBuf内从0x0D 0x0A更改为0x0A,然后进行翻译。 (但是当chBuf以0x0D结尾时要小心...)

(某些技术可以将少量代码“注入(inject)”到Windows所控制的另一个程序中。它们有些危险,也很麻烦。如果您真的不满意翻译的想法,则可以查找“DLL注入(inject)”。)

关于c++ - Win32更改为二进制模式子级的Stdout(管道),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/18294650/

10-10 11:24