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 < result.out.with_bad_newlines > result.out
。如果由于某种原因你不能这样做,你可以在你写出来之前让你的程序在 chBuf
中将 0x0D 0x0A 更改为 0x0A,边做边翻译。 (但当 chBuf ends 在 0x0D 时要小心...)
(有些技术可以将一点代码“注入”到另一个您在 Windows 上控制的程序中。它们有点危险,也很麻烦。如果您对翻译想法真的不满意,你可以查一下“DLL注入”)
【讨论】:
谢谢!还有一件事,我的孩子正在用二进制模式写作,这个解决方案也适用于二进制模式吗? 我以为你说孩子在文本模式下有标准输出。如果孩子的标准输出是二进制模式,那么我不明白你为什么会看到 0x0D 0x0A 。你的问题现在很混乱,似乎自相矛盾。如果流是文本模式流,则不能以二进制模式写入,并且如果您没有调用_setmode(stdout, O_BINARY)
,则 stdout 流是文本模式流。
糟糕,抱歉。我应该说:孩子写的是字节而不是文本。我添加了最终解决方案。再次感谢。以上是关于Win32 更改为二进制模式子的标准输出(管道)的主要内容,如果未能解决你的问题,请参考以下文章
Windows IPC:我可以通过匿名管道发送二进制数据吗?
在重定向的stdout管道上禁用缓冲(Win32 API,C ++)