コマンドラインツールを実行するアプリケーションを作成する場合、そのツールが出力するメッセージを表示、及び取得する必要があります。DOSCommandコンポーネントを別の記事で紹介していますこのDOSCommandコンポーネントを使用すれば簡単に作成することが可能です。
この記事では、WIN32のAPIのパイプ処理を利用した標準出力を取得する方法について説明します。
使用するAPI関数
パイプ処理には、CreatePipe関数を使用します。また、コマンドの実行は、CreateProcess関数を使用します。
サンプルコード
サンプルコードです。
create_cmd_process関数
CreatePipe関数、及び CreateProcess関数を使用して、コマンドを実行してその出力をTMemoに表示します。
// ----------------------------------------------------------------------------
/**
* @brief コマンドラインの実行ファイルを実行する
*
* @param[in,out] TMemo* Memo : ツールからのメッセージ(標準出力)を表示するTMemoコンポーネント
* @param[in] String opt : 実行するコマンドの文字列
* @param[in,out] TStringList* PipeString : ツールからのメッセージ(標準出力)を格納するTStringList
*
* @return bool __fastcall true:成功 / false:失敗
*/
// ----------------------------------------------------------------------------
bool __fastcall TSDIAppForm::create_cmd_process(TMemo* Memo, String opt, TStringList *PipeString)
{
String wstr = "";
String wstr2 = "";
int leng;
// パイプの作成
HANDLE readTemp;
HANDLE readPipe;
HANDLE writePipe;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
if (CreatePipe(&readTemp, &writePipe, &sa,0) == 0)
{
MessageBox(0, _TEXT("パイプが作成できませんでした"), _TEXT("エラー"), MB_OK);
return false;
}
// 読込ハンドルを複製(子プロセスへ継承可能な権限の読込ハンドルを作成)
if (!DuplicateHandle(
GetCurrentProcess(), readTemp,
GetCurrentProcess(), &readPipe,
0, TRUE, DUPLICATE_SAME_ACCESS) )
{
if (!CloseHandle(readTemp))
{
//
}
return false;
}
// 複製元のハンドルは使わないのでクローズ
if (!CloseHandle(readTemp))
{
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;
String CmdStr = (String)opt;
if (CreateProcess(NULL, CmdStr.c_str(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) == 0) // if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) == 0)
{
MessageBox(0, _TEXT("プロセスの作成に失敗しました"), _TEXT("エラー"), MB_OK);
return false;
}
try
{
HANDLE childProcess = pi.hProcess;
CloseHandle(pi.hThread);
char readBuf[1025*2];
bool end = false;
do
{
DWORD totalLen, len;
if(::MsgWaitForMultipleObjects(1, &childProcess, FALSE, INFINITE, QS_ALLINPUT ) == 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(len > 1) // len : 0 の場合がある
{
PipeString->Add((String)readBuf);
if(Memo != NULL)
{
#if 0
Memo->Lines->Add(readBuf);
#else
String SrcStr = (String)readBuf;
String SearchStr = "\r\n";
String SubStr = "";
while(1)
{
if(GetSubString(SrcStr, SubStr, SearchStr))
{
Memo->Lines->Add(SubStr);
} else {
break;
}
Application->ProcessMessages();
}
if(SrcStr.Length())
{
Memo->Lines->Add(SrcStr);
}
#endif
}
}
if(totalLen > len) // プロセスは終了しているがまだデータがーが残っているので終了を保留
{
end = false;
}
}
Application->ProcessMessages();
} while(end == false);
} __finally {
if (CloseHandle(writePipe) == 0)
{
CloseHandle(pi.hProcess);
MessageBox(0, _TEXT("パイプを閉じることができませんでした。"), _TEXT("エラー"), MB_OK);
// return false;
}
if (CloseHandle(readPipe) == 0)
{
CloseHandle(pi.hProcess);
MessageBox(0, _TEXT("パイプを閉じることができませんでした。"), _TEXT("エラー"), MB_OK);
// return false;
}
CloseHandle(pi.hProcess);
}
return true;
}
GetSubString関数
ReadFile関数でパイプから取得した文字列を1行毎に分割する関数です。
// ----------------------------------------------------------------------------
/**
* @brief 指定文字の前後の文字列に分割する
*
* @param[in,out] String& SrcStr 分割後の後ろの部分の文字列(指定文字がない場合は、元の文字列)
* @param[in,out] String& SubStr 分割後の前の部分の文字列
* @param[in] String SearchStr 分割する指定文字
*
* @return int 0 : 指定文字なし
*/
// ----------------------------------------------------------------------------
int __fastcall TSDIAppForm::GetSubString(String &SrcStr, String &SubStr, String SearchStr)
{
int pos = SrcStr.Pos(SearchStr);
if(pos != 0)
{
String wstr = SrcStr;
SubStr = wstr.SubString(1, pos -1);
SrcStr = SrcStr.Delete(1, pos +1 );
} else {
SubStr = "";
}
return pos;
}
コマンドを実行するサンプルコード
「Ping」と「dir」 を実行するメニューを追加して実行しています。
DOSコマンドは、「cmd.exe /C」の引数として渡すことで実行できます。
void __fastcall TSDIAppForm::Ping1Click(TObject *Sender)
{
String CmdStr = "ping google.com";
TStringList *PipeString = new TStringList;
create_cmd_process(Memo1, CmdStr, PipeString);
delete PipeString;
}
//---------------------------------------------------------------------------
void __fastcall TSDIAppForm::dir1Click(TObject *Sender)
{
String CmdStr = "cmd.exe /C dir";
TStringList *PipeString = new TStringList;
create_cmd_process(Memo1, CmdStr, PipeString);
delete PipeString;
}
サンプルプロジェクトのソースコード
SDIアプリケーションをベースに作成したサンプルプリジェクトのソースコードは以下からダウンロードできます。
DOSCmd2_sample
CreatePipe関数, CreateProcess関数を使用した、コマンドラインツールを実行するアプリのサンプルプロジェクトです。
参考URL
以下、参考URLです。