むか〜し、コマンドプロンプトの出力を、ファイルに保存するってネタを書いた事がありました。
先日、標準入出力をリダイレクトするやり方を、もう一度おさらいしていたら、MSKBのコードを一部いじるだけで、所望の機能を実現できる事に気づきました。
- コンソール プロセスを生成して標準ハンドルをリダイレクトする方法
http://support.microsoft.com/kb/190351/ja
あちゃ〜、何で今まで気づかなかったんだろう?ってな感じです。
欲しいモノは、実は目の前にあったとは。(これも 青い鳥 の一種かな)
という訳で、MSKBのコードを改造して、標準入出力をファイルに保存できるようにしたコードを、以下に貼り付けておきます。
/* 以下の改造版。標準入出力を指定したログファイル(argv[1])に保存します コンソール プロセスを生成して標準ハンドルをリダイレクトする方法 http://support.microsoft.com/kb/190351/ja */ /*++ Copyright (c) 1998 Microsoft Corporation Module Name: Redirect.c Description: This sample illustrates how to spawn a child console based application with redirected standard handles. The following import libraries are required: user32.lib Dave McPherson (davemm) 11-March-98 --*/ #include<windows.h> #include<stdio.h> #pragma comment(lib, "User32.lib") void DisplayError(char *pszAPI); void ReadAndHandleOutput(HANDLE hPipeRead); void PrepAndLaunchRedirectedChild(HANDLE hChildStdOut, HANDLE hChildStdIn, HANDLE hChildStdErr); DWORD WINAPI GetAndSendInputThread(LPVOID lpvThreadParam); HANDLE hChildProcess = NULL; HANDLE hStdIn = NULL; // Handle to parents std input. BOOL bRunThread = TRUE; HANDLE g_hLogFile = NULL; void main (int argc, char* argv[]) { HANDLE hOutputReadTmp,hOutputRead,hOutputWrite; HANDLE hInputWriteTmp,hInputRead,hInputWrite; HANDLE hErrorWrite; HANDLE hThread; DWORD ThreadId; SECURITY_ATTRIBUTES sa; // Set up the security attributes struct. sa.nLength= sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; // Create the child output pipe. if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,&sa,0)) DisplayError("CreatePipe"); // Create a duplicate of the output write handle for the std error // write handle. This is necessary in case the child application // closes one of its std output handles. if (!DuplicateHandle(GetCurrentProcess(),hOutputWrite, GetCurrentProcess(),&hErrorWrite,0, TRUE,DUPLICATE_SAME_ACCESS)) DisplayError("DuplicateHandle"); // Create the child input pipe. if (!CreatePipe(&hInputRead,&hInputWriteTmp,&sa,0)) DisplayError("CreatePipe"); // Create new output read handle and the input write handles. Set // the Properties to FALSE. Otherwise, the child inherits the // properties and, as a result, non-closeable handles to the pipes // are created. if (!DuplicateHandle(GetCurrentProcess(),hOutputReadTmp, GetCurrentProcess(), &hOutputRead, // Address of new handle. 0,FALSE, // Make it uninheritable. DUPLICATE_SAME_ACCESS)) DisplayError("DupliateHandle"); if (!DuplicateHandle(GetCurrentProcess(),hInputWriteTmp, GetCurrentProcess(), &hInputWrite, // Address of new handle. 0,FALSE, // Make it uninheritable. DUPLICATE_SAME_ACCESS)) DisplayError("DupliateHandle"); // Close inheritable copies of the handles you do not want to be // inherited. if (!CloseHandle(hOutputReadTmp)) DisplayError("CloseHandle"); if (!CloseHandle(hInputWriteTmp)) DisplayError("CloseHandle"); // Get std input handle so you can close it and force the ReadFile to // fail when you want the input thread to exit. if ( (hStdIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE ) DisplayError("GetStdHandle"); // ログファイルを作成する fprintf(stderr, "$$$ 標準入出力のフックを開始します(exitで終了)\n"); if(argc > 1){ const char* log_file = argv[1]; printf("arg[1] = %s\n", log_file); g_hLogFile = ::CreateFile(log_file, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(g_hLogFile == INVALID_HANDLE_VALUE) DisplayError("fail log file"); fprintf(stderr, "$$$ 標準入出力を%sに保存開始しました\n", log_file); } PrepAndLaunchRedirectedChild(hOutputWrite,hInputRead,hErrorWrite); // Close pipe handles (do not continue to modify the parent). // You need to make sure that no handles to the write end of the // output pipe are maintained in this process or else the pipe will // not close when the child process exits and the ReadFile will hang. if (!CloseHandle(hOutputWrite)) DisplayError("CloseHandle"); if (!CloseHandle(hInputRead )) DisplayError("CloseHandle"); if (!CloseHandle(hErrorWrite)) DisplayError("CloseHandle"); // Launch the thread that gets the input and sends it to the child. hThread = CreateThread(NULL,0,GetAndSendInputThread, (LPVOID)hInputWrite,0,&ThreadId); if (hThread == NULL) DisplayError("CreateThread"); // Read the child's output. ReadAndHandleOutput(hOutputRead); // Redirection is complete // Force the read on the input to return by closing the stdin handle. if (!CloseHandle(hStdIn)) DisplayError("CloseHandle"); // Tell the thread to exit and wait for thread to die. bRunThread = FALSE; if (WaitForSingleObject(hThread,INFINITE) == WAIT_FAILED) DisplayError("WaitForSingleObject"); if (!CloseHandle(hOutputRead)) DisplayError("CloseHandle"); if (!CloseHandle(hInputWrite)) DisplayError("CloseHandle"); if(g_hLogFile != NULL){ if (!CloseHandle(g_hLogFile)) DisplayError("CloseHandle"); fprintf(stderr, "$$$ 標準入出力のログファイル保存が終了しました\n"); } fprintf(stderr, "$$$ bye,bye\n"); } /////////////////////////////////////////////////////////////////////// // PrepAndLaunchRedirectedChild // Sets up STARTUPINFO structure, and launches redirected child. /////////////////////////////////////////////////////////////////////// void PrepAndLaunchRedirectedChild(HANDLE hChildStdOut, HANDLE hChildStdIn, HANDLE hChildStdErr) { PROCESS_INFORMATION pi; STARTUPINFO si; // Set up the start up info struct. ZeroMemory(&si,sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESTDHANDLES; si.hStdOutput = hChildStdOut; si.hStdInput = hChildStdIn; si.hStdError = hChildStdErr; // Use this if you want to hide the child: // si.wShowWindow = SW_HIDE; // Note that dwFlags must include STARTF_USESHOWWINDOW if you want to // use the wShowWindow flags. // Launch the process that you want to redirect (in this case, // Child.exe). Make sure Child.exe is in the same directory as // redirect.c launch redirect from a command line to prevent location // confusion. char cmd_buff[1024] = "child.exe"; if(!GetEnvironmentVariable("COMSPEC", cmd_buff, 32768)) strcpy_s(cmd_buff, sizeof(cmd_buff) / sizeof(cmd_buff[0]), "cmd.exe"); if (!CreateProcess(NULL,cmd_buff,NULL,NULL,TRUE, CREATE_NO_WINDOW,NULL,NULL,&si,&pi)) DisplayError("CreateProcess"); // Set global child process handle to cause threads to exit. hChildProcess = pi.hProcess; // Close any unnecessary handles. if (!CloseHandle(pi.hThread)) DisplayError("CloseHandle"); } /////////////////////////////////////////////////////////////////////// // ReadAndHandleOutput // Monitors handle for input. Exits when child exits or pipe breaks. /////////////////////////////////////////////////////////////////////// void ReadAndHandleOutput(HANDLE hPipeRead) { CHAR lpBuffer[256]; DWORD nBytesRead; DWORD nCharsWritten; while(TRUE) { if (!ReadFile(hPipeRead,lpBuffer,sizeof(lpBuffer), &nBytesRead,NULL) || !nBytesRead) { if (GetLastError() == ERROR_BROKEN_PIPE) break; // pipe done - normal exit path. else DisplayError("ReadFile"); // Something bad happened. } // Display the character read on the screen. if (!WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),lpBuffer, nBytesRead,&nCharsWritten,NULL)) DisplayError("WriteConsole"); // save log if(g_hLogFile != NULL){ ::WriteFile(g_hLogFile, lpBuffer, nBytesRead, &nCharsWritten, NULL); } } } /////////////////////////////////////////////////////////////////////// // GetAndSendInputThread // Thread procedure that monitors the console for input and sends input // to the child process through the input pipe. // This thread ends when the child application exits. /////////////////////////////////////////////////////////////////////// DWORD WINAPI GetAndSendInputThread(LPVOID lpvThreadParam) { CHAR read_buff[256]; DWORD nBytesRead,nBytesWrote; HANDLE hPipeWrite = (HANDLE)lpvThreadParam; // Get input from our console and send it to child through the pipe. while (bRunThread) { if(!ReadConsole(hStdIn,read_buff,1,&nBytesRead,NULL)) DisplayError("ReadConsole"); read_buff[nBytesRead] = '\0'; // Follow input with a NULL. if (!WriteFile(hPipeWrite,read_buff,nBytesRead,&nBytesWrote,NULL)) { if (GetLastError() == ERROR_NO_DATA) break; // Pipe was closed (normal exit path). else DisplayError("WriteFile"); } } return 1; } /////////////////////////////////////////////////////////////////////// // DisplayError // Displays the error number and corresponding message. /////////////////////////////////////////////////////////////////////// void DisplayError(char *pszAPI) { LPVOID lpvMessageBuffer; CHAR szPrintBuffer[512]; DWORD nCharsWritten; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpvMessageBuffer, 0, NULL); wsprintf(szPrintBuffer, "ERROR: API = %s.\n error code = %d.\n message = %s.\n", pszAPI, GetLastError(), (char *)lpvMessageBuffer); WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE),szPrintBuffer, lstrlen(szPrintBuffer),&nCharsWritten,NULL); LocalFree(lpvMessageBuffer); ExitProcess(GetLastError()); }
ちなみに、以下のやり方で改造しました。
- VC2008を起動
- 新しいプロジェクトの追加>Visual C++>Win32コンソールアプリケーションを選択
- アプリケーションの設定では、
コンソールアプリケーション
空のプロジェクト
を選択 - プロジェクトが出来たら、CPPのファイルを新規に作成
- MSKBのコードを貼り付ける
- CTRL+Aでコード全体を選択
- 編集>詳細>選択範囲のフォーマットを選択し、ソースコードを整頓
- プロジェクトの設定で、構成プロパティ>全般>文字セットを「マルチバイト文字セットを使用する」に変更
- ビルド出来る事を確認
- 後は、改造しまくる
一昔前は、テキストエディタでごしごしやっていたんですが、最近IDEのコード補完を使っている自分に、ちょっと苦笑。
昔、使わなかったのは、IDEが遅い、スケルトンから作成に入らないとコード作成できない、等の制約があったからなんですが、最近のIDEは少々の無理を聞いてくれるようになったのが嬉しいですね。