概要

Windowsの機能であるパイプを用いて子プロセスの標準出力と標準エラー出力のデータを取得しエディットボックスに表示します。 ここでは子プロセスとしてcmd.exeを使用しています。
あたかも、ダイアログボックスの中にコマンドプロンプトが存在しているように見えます。
コマンドをエディットボックスに入力してRETURNキーを押すか、実行をクリックするとコマンドが実行されまs。
実行結果は、下のエディットボックスに表示されます。コマンドの実行中は実行(R)ボタンが無効になります。
一回コマンドごとにcmd.exeを起動するので、カレントディレクトリを変更するコマンド等を実行しても次のコマンド実行には反映されません。
コマンドは、cmd.exeの/Kオプションを使用して実行しています。
変更不可のエディットボックスに結果を表示していますので、実行結果を普通にコピーすることが可能です。

テスト環境

コンパイラ

Visual C++ 2008 Standard 32/64bit
Visual C++ 2013 Express 32/64bit

プロジェクトの作成

Win32プロジェクト Windowsアプリケーション

実行環境

Windows 8.1 Enterprise 64bit
Windows 7 EnterPrise Service Pack 1 64bit
Windows Vista Ultimate Service Pack 2 32bit
Windows XP Professional Service Pack 3 32bit

プログラムソースの概要

cmdpipe1.cpp

_tWinMain関数

Windowsから最初に_tWinMain関数が呼び出されます。
DialogBox APIを呼び出してモーダルダイアログボックスを表示します。

DlgProc1

ダイアログボックスプロシージャーです。 必要な時にWindowsから呼び出されます。 第2引数にメッセージの種類が格納されていますので、switchステートメントによりメッセージごとの処理を振り分けます。 コントロールダイアログボックスに張り付けられているエディットボックスやプッシュボタン等 コントロールと呼ぶ)を操作するには、コントロールのハンドルが必要です。 ダイアログボックスのコントロール(子ウィンドウ)のハンドルを取得するには、 GetDlgItem APIを呼び出し、ダイアログボックスのハンドルとコントロールのID番号よりハンドルを取得します。 自分で処理しないメッセージは、ダイアログボックスプロシージャー終了時にFALSEを渡せば、Windowsが標準的な処理を行ってくれます。
case WM_INITDIALOG:
ダイアログボックスの初期化時に発生するメッセージです。
エディットボックスの値を保存するバッファmemを初期化します。
case WM_COMMAND:
ダイアログボックスの子ウィンドウ(ダイアログボックスに張り付けられているエディットボックスやプッシュボタン等)から発生したメッセージが届きます。 ダイアログボックスプロシージャーの第3引数であるWPARAMの下位16bitがコントロールのID番号、その上位16bitには通知内容が格納されています。 LOWORDマクロによりWPARAMの下位16bitを取り出し、switchステートメントによりコントロールごとに処理を振り分けます。
case IDOK:
実行ボタンのクリックまたは、RETURNキーを押したときに呼び出されます。
GetWindowText APIによりエディットボックスからコマンド文字列を取得します。
EnableWindow APIによりエディットボックスと実行ボタンを無効化します。
create_cmd_process関数を呼び出し、cmd.exeを起動し実行結果をエディットボックスに表示します。
EnableWindow APIによりエディットボックスと実行ボタンを有効にします。
SetFocus APIによりフォーカスをエディットボックスに設定します。
case IDCANCEL:
終了ボタンまたはESCキーを押したときに呼び出されます。
EndDialog APIを呼び出し、ダイアログボックスを終了させます。 このAPIの第2引数は、ダイアログボックスが終了し、DialogBox APIが返す値となります。

create_cmd_process

CreateProcess APIで子プロセスを起動するときに、使用する標準入出力等のハンドルを渡すことができます。
まず、CreatePipe APIにより標準出力・標準エラー出力で使用するパイプを作成します。
CreateProcess APIに渡すSTARTUPINFO構造体にパイプを設定します。
子プロセスでコンソールが表示されないように、wShowWindowメンバーにSW_HIDEを設定します。
CreateProcess APIでcmd.exeを起動します。この際、cmd.exeを直接以下のように文字列定数を記述して呼び出すと実行時にエラーが発生します。
CreateProcess(NULL, _TEXT("cmd.exe"), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)

これは、第2引数の文字列は書き換え可能なメモリでなければならず、文字列定数の場合、書き込み禁止なので、書き込みできない旨のエラーが出力されるわけです。
一度書き換え可能なメモリにコピーしてCreateProcess APIに渡せば問題ありません。
以降、子プロセスが実行中であることパイプに標準出力・標準エラー出力からのデーターがあるかどうか確認しデータを読み込みながらエディットボックスに表示するループとなっています。
WaitForSingleObject APIにより子プロセスが実行中であるかどうか調べます。すでに終了している場合はWAIT_OBJECT_0が返されますので、終了であることを示すプル型endにtrueをセットします。
PeekNamedPipe APIによりパイプにデータがどれだけ残っているかチェックします。
データーがある場合、ReadFile APIによりパイプのデータを取得します。
ここでは、パイプのデータを保存するメモリmemのサイズを固定としていますので、あふれる場合は、メモリmemをクリアしています。
取得したデータはANSI文字列なので、strcat_sを使用してメモリmemの末尾に追加します。
SetWindowTextA APIによりメモリmemをエディットボックスに表示しています。
エディットボックスには絶えず、最新のデーターが表示されるようにSendMessage APIによりエディットボックスの鉛直スクロールバーを一番下へ移動させます。
子プロセスが終了してもパイプにデーターが残っている場合があるので、PeekNamedPipe APIで取得した残りバイト数とReadFile APIで実際に読み取ったバイト数を比較し、残りがある場合は、プル型endにfalseをセットし、ループが継続するようにします。
読み取るデータがないとループが終了するので、パイプのハンドルとプロセスのハンドルを開放します。

プログラムソース

cmdpipe1.cpp

//	パイプにより子プロセスの標準出力と標準エラー出力の内容を読み取りダイアログボックスのエディットボックスに表示
//	Visual C++ 2008/2013 32bit/64bit

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include "resource.h"

char mem[80*25*10];	//	標準出力のバッファ

DWORD memSz=0;	//	標準出力のバッファの有効データー数


//	ダイアログボックスプロシージャー
LRESULT CALLBACK DlgProc1(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

//	パイプを作成しコマンドプロンプトの出力をhWndに表示
bool create_cmd_process(HWND hWnd,TCHAR* opt);


int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst, TCHAR* lpszCmdLine, int nCmdShow){
	DialogBox(hInstance, TEXT("CMD_DLG"), 0, (DLGPROC)DlgProc1);
	return (int)0;
}

//	ダイアログボックスプロシージャー

LRESULT CALLBACK DlgProc1(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam){
	TCHAR opt[128];
	switch (msg) {
	case WM_INITDIALOG:
		mem[0] = 0;
		return TRUE;
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDOK:	//	コマンドの実行
			GetWindowText(GetDlgItem(hDlg, IDC_IN_EDIT), opt, sizeof(opt) / sizeof(TCHAR));
			EnableWindow(GetDlgItem(hDlg, IDOK), FALSE);
			EnableWindow(GetDlgItem(hDlg, IDC_IN_EDIT), FALSE);
			create_cmd_process(GetDlgItem(hDlg, IDC_OUT_EDIT),opt);
			EnableWindow(GetDlgItem(hDlg, IDOK), TRUE);
			EnableWindow(GetDlgItem(hDlg, IDC_IN_EDIT), TRUE);
			SetFocus(GetDlgItem(hDlg, IDC_IN_EDIT));
			return TRUE;
		case IDCANCEL:
			EndDialog(hDlg, FALSE);
			return FALSE;
		default:
			return FALSE;
		}
	default:
		return FALSE;
	}
	return TRUE;
}

//	パイプを作成しコマンドプロンプトの出力をhWndに表示

bool create_cmd_process(HWND hWnd,TCHAR* opt){
	//	パイプの作成
	HANDLE readPipe;
	HANDLE writePipe;
	SECURITY_ATTRIBUTES sa;
	sa.nLength = sizeof(sa);
	sa.bInheritHandle = TRUE;
	sa.lpSecurityDescriptor = NULL;
	if (CreatePipe(&readPipe, &writePipe, &sa,0) == 0){
		MessageBox(0, _TEXT("パイプが作成できませんでした"), _TEXT("エラー"), MB_OK);
		return false;
	}
	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	ZeroMemory(&si, sizeof(si));
	ZeroMemory(&pi, sizeof(pi));
	si.cb = sizeof(si);
	si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
	si.hStdOutput = writePipe;
	si.hStdError = writePipe;
	si.wShowWindow = SW_HIDE;
	TCHAR cmd[MAX_PATH];
//	プロセスの起動(cmd.exe)
	_stprintf_s(cmd, sizeof(cmd) / sizeof(TCHAR), _TEXT("cmd.exe /K %s"),opt);
	if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) == 0){
		MessageBox(0, _TEXT("プロセスの作成に失敗しました"), _TEXT("エラー"), MB_OK);
		return false;
	}
	HANDLE childProcess = pi.hProcess;
	CloseHandle(pi.hThread);
	char readBuf[1025];
	bool end=false;
	do{
		DWORD totalLen,len;
		if (WaitForSingleObject(childProcess, 100) == WAIT_OBJECT_0)
			end=true;
		if (PeekNamedPipe(readPipe, NULL, 0, NULL, &totalLen, NULL) == 0)
			break;
		if (0 < totalLen){
			if (ReadFile(readPipe, readBuf, sizeof(readBuf) - 1, &len, NULL) == 0)
				return false;
			readBuf[len] = 0;

			if(sizeof(mem)-1<memSz+len){	//メモリがあふれないようにクリアする
				mem[0]=0;
				memSz=0;
			}
			memSz+=len;
			strcat_s(mem, sizeof(mem), readBuf);
			SetWindowTextA(hWnd, mem);
			SendMessage(hWnd, WM_VSCROLL, SB_BOTTOM, 0);	//	スクロールバーを一番下へ移動させる
			if(totalLen>len)	//	プロセスは終了しているがまだデータがーが残っているので終了を保留
				end=false;
		}
	}while(end==false);

	if (CloseHandle(writePipe) == 0){
		MessageBox(0, _TEXT("パイプを閉じることができませんでした。"), _TEXT("エラー"), MB_OK);
		return false;
	}
	if (CloseHandle(readPipe) == 0){
		MessageBox(0, _TEXT("パイプを閉じることができませんでした。"), _TEXT("エラー"), MB_OK);
		return false;
	}
	CloseHandle(pi.hProcess);

	return true;
}

resource.h

#define IDC_OUT_EDIT 100
#define IDC_IN_EDIT 110

resource.rc

#include <windows.h>
#include "resource.h"

CMD_DLG DIALOG DISCARDABLE 0, 0, 380, 282
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_SETFONT
CAPTION "コンソールの出力のパイプテスト"
FONT 9, "MS Shell Dlg"
{
	CONTROL "コマンド", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 7, 7, 57, 12
	CONTROL "", IDC_IN_EDIT, "EDIT", WS_CHILD | WS_DLGFRAME | WS_VISIBLE | ES_AUTOHSCROLL, 7, 19, 311, 12
	CONTROL "実行(&R)", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 325, 19, 48, 14

	CONTROL "標準出力・標準エラー出力", -1, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 7, 40, 128, 12
	CONTROL "", IDC_OUT_EDIT, "EDIT", WS_CHILD | WS_DLGFRAME | WS_VISIBLE | WS_VSCROLL | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY, 7, 52, 362, 200

	CONTROL "終了(&X)", IDCANCEL, "BUTTON", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 163, 261, 54, 14
}

ソースファイルと実行ファイルのダウンロード

ダウンロード cmdpipe1.zip(30.9kByte)

ZIPファイルに含まれるファイル
cmdpipe1.cpp
cmdpipe.exe
resource.h
resource.rc