Win32 - transition to binary mode - child Stdout (pipe)

Hail this great community

I'm having problems automatically converting ('\n') 0x0A to ('\n\r') 0x0D 0x0A when using a channel to redirect a child stdout to a file, the output of the child is bytes, not text.

First, I used these MSDN examples - creating a child process with redirected inputs and outputs and http://support.microsoft.com/kb/190351 ), and now I have this basic application, it creates a channel and redirects the child STDOUT to a binary file. All this in the Win32 Console Application in Visual C ++ 6.0 (yes, this is old, but it is a requirement).

 #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 environment NULL, // use parent 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); } 

And in this child.cpp dummy, you will notice that if I set STDOUT to binary mode, everything will work fine (I only get 0x0A 0x0A!), But my real child is EXE, and I do not have access to this code.

 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; } 

So, after searching for about 2 days and considering that I have basic C ++ knowledge, I ask: is there a way I could do _setmode for childs stdout from the parent, given that I do not have access to the child code.

As a solution, I seriously consider looking for all '0x0D' '0x0A' and replacing it with '0x0A' . I'm really crazy about this problem ... Therefore, if someone can help me, I will be very grateful.

A related question: Win32 thread handles - transition to binary mode , but it has access to the child code!

Edit

As a point of @librik, the final solution would be to replace every single occurrence of 0x0D 0x0A with 0x0A. For this to work, the contents of the file must be in memory. There are certain problems, but I can live with it (excess of allocated memory). Hope this will be helpful:

 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
source share
1 answer

Your problem is that the “stream mode” is not part of Windows, so you cannot change it from outside another program. This is part of the C and C ++ system, and therefore it is a private part of every single C or C ++ program that you run.

There is a library of functions that integrates with every program compiled in C ++, called the "C ++ Standard Library". The C ++ Standard Library contains all the functions for streams such as stdout . It is inside another standard C ++ library, where 0x0A is converted to 0x0D 0x0A before being written to the stream. _setmode is a function inside the C ++ standard library that turns this conversion on and off, therefore, adding to it a call inside child.cpp that tells child.cpp to the C ++ standard library to leave only stdout . But you have no way to force another program to call the _setmode function.

So, the best thing you need is the "crazy" solution you proposed:

As a solution, I am seriously thinking about finding all the "0x0D" 0x0A 'and replacing it with "0x0A".

As long as you know that child.exe writes in text mode, not binary mode, then every single occurrence of 0x0D 0x0A should initially be one 0x0A. (If the program tried to write two bytes 0x0D 0x0A, it would exit like three bytes 0x0D 0x0D 0x0A.) Therefore, you are absolutely safe and correct to "fix" the result by contacting back.

I think the easiest approach is to simply write result.out just like you are doing it now, but then translate 0x0D 0x0A to 0x0A at the end, creating a new file that is correct. There are small software programs that you can download that will do such a thing for you - one of them is called dos2unix . This is actually the easiest way - just take the last step of your dos2unix < result.out.with_bad_newlines > result.out program dos2unix < result.out.with_bad_newlines > result.out . If for some reason you cannot do this, you can change your program from 0x0D 0x0A to 0x0A inside chBuf before you chBuf it out, translating how you go. (But be careful when chBuf ends at 0x0D ...)

(There are certain methods that can “inject” a little code into another program that is under your control on Windows. They are a bit dangerous and a lot of trouble. If you are really unhappy with the idea of ​​translating, you can find a “DLL injection”.)

+2
source

Source: https://habr.com/ru/post/1497625/


All Articles