2024/06/03 電子書籍「電子書籍出版・技術文書作成を劇的に加速!秀丸エディタ + Markdown + Pandocの驚異」を出版

コマンドラインツールを実行するアプリの作成

C++Builder

コマンドラインツールを実行するアプリケーションを作成する場合、そのツールが出力するメッセージを表示、及び取得する必要があります。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です。

タイトルとURLをコピーしました