自作アプリでDOSコマンドの表示結果を取得する方法
すいません m(_ _)m、本記事はブログ引越時に書式が崩れました。順次修正中です。
えーっと、久しぶりに、ちまちまっとプログラムなんか書いていたりします(実は新型機の上半身設計が悩ましすぎて飽きてきた…)。何をやっていたかといいますと、コンソールプログラム…「DOSコマンド」と勝手に呼んでますけど…の実行結果を、WideStudioで作成したウィンドウに表示するプログラムです。うーん、なんと説明したらよいか…(そもそも、DOSコマンドという用語が通じるのかな)。
WideStudioもそうですが、よく統合環境なんかで、GCCのコンパイル結果なんかを統合環境のウィンドウに表示する機能があります。あれを自分で作成したプログラムでやってみようというチャレンジです。普通にDOSコマンドを実行すると、DOS窓が開いて、そこで実行されますが、統合環境みたいなので、いちいちDOS窓が開いて実行、というのはちょっとかっこ悪いかなぁと。また、実行結果を使って何かしたくても、DOS窓で実行されちゃうとなんともなりませんし。
どうやってやるのかな?とネットで調べてみたのですが、意外と情報がありませんでした。で、ようやく見つけたのがマイクロソフト社の「コンソールプロセスを生成して標準ハンドルをリダイレクトする方法」です。でもなんだかこのサンプル、基本を理解するには難しいです。それでも「Pipeがどうのこうのでリダイレクトして云々」というポイントがわかりましたので、さらにこれらのキーワードでグルグルしてみたところ、Denasu Systemさんという方の「モルタルコのプログラマ日記 2000.05」さんという方のホームページで紹介されているのを発見し、これをだいぶ参考にさせていただいてプログラムを作ってみました。
プログラムはWindows APIべたべたですので、WideStudioじゃなくてもコアな部分は動きます(逆に言うと、WideStudioのプラットホームポータビリティが無いサンプル)。実際に作ったプログラムは、環境変数設定とか行末へリアルタイムに移動したりなどあれこれしていますが、ややこしくなるので、一番シンプルな状態にしてあります。
まずは実行結果から紹介します。なつかしのDOSコマンド、「TREE」の実行結果です。なんか個別情報みたいのも表示されるので、少しスクロールした画面を貼り付けています(あと、なぜか実行すると横スクロールしちゃうので、それも戻してますけど)が、ちゃんとTREEの実行結果が表示されているのがわかると思います。
WideStudioのプロジェクトとしては、単純にメインウィンドウを作成し、それにテキストフィールド(WSCtextField)とボタンを貼り付け、ボタンに実行用のイベントプロシージャを書いただけのものです。以下、イベントプロシージャ部分のソースコードです。テキストフィールドは「Maitext_000」という名前にしてあります。
#include <WScom.H>
#include <WSCfunctionList.H>
#include <WSCbase.H>
#include <WSCtextField.H>
extern WSCtextField* Maitext_000;
//———————————————————-
//Function for the event procedure
//———————————————————-
void Mainvbtn_001_ACTIVATE_EP( WSCbase* object ){
// Windows APIアクセス用変数
HANDLE hReadPipe = NULL, // 標準入力用パイプ
hWritePipe = NULL; // 標準出力用パイプ
HANDLE hErrReadPipe = NULL, // エラー入力用パイプ
hErrWritePipe = NULL; // エラー出力用パイプ
DWORD dwRes;
SECURITY_ATTRIBUTES sa; // セキュリティ属性
STARTUPINFO si; // 起動ウィンドウ情報
PROCESS_INFORMATION pi; // 起動プロセス情報
// パイプ内容受け取り用バッファ
char szStdOut[8192], szErrOut[8192];
DWORD dwStdOut = 0, dwErrOut = 0;
DWORD dwRet;
// テキストボックスのクリア
Maitext_000->setProperty( WSNlabelString, “” );
// セキュリティ属性(ハンドル継承を指定)
sa.nLength = sizeof( SECURITY_ATTRIBUTES );
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
// 標準、エラー出力パイプの作成
::CreatePipe( &hReadPipe, &hWritePipe, &sa, 0 );
::CreatePipe( &hErrReadPipe, &hErrWritePipe, &sa, 0 );
::ZeroMemory( &si, sizeof(STARTUPINFO));
si.cb = sizeof( STARTUPINFO );
si.dwFlags = STARTF_USESTDHANDLES; // ハンドルの継承を指定
si.wShowWindow = SW_HIDE; // DOS窓を表示しない
si.hStdOutput = hWritePipe; // 標準出力ハンドル設定
si.hStdError = hErrWritePipe; // エラー出力ハンドル設定
// コンソールアプリ起動
if (::CreateProcess( NULL, “tree.com”, NULL, NULL, TRUE, CREATE_NO_WINDOW,
NULL, NULL,
&si, &pi )){
// プロセス起動中のパイプ内容受け取り処理
while ( (dwRet = ::WaitForSingleObject( pi.hProcess, 0)) != WAIT_ABANDONED){
::ZeroMemory( szStdOut, sizeof(szStdOut));
::ZeroMemory( szErrOut, sizeof(szErrOut));
// 標準出力パイプの内容を調べる
::PeekNamedPipe( hReadPipe, NULL, 0, NULL, &dwStdOut, NULL );
if( dwStdOut > 0 ){
// 内容が存在すれば、読み取る
::ReadFile( hReadPipe, szStdOut, sizeof(szStdOut) – 1, &dwStdOut, NULL );
// szStdOut にパイプ出力が入る
Maitext_000->addString((char*)szStdOut );
}
// 同様にエラー出力の処理
::PeekNamedPipe( hErrReadPipe, NULL, 0, NULL, &dwErrOut, NULL );
if( dwErrOut > 0 ){
::ReadFile( hErrReadPipe, szErrOut, sizeof(szErrOut) – 1, &dwErrOut, NULL );
// szErrOut にパイプ出力が入る
Maitext_000->addString((char*)szErrOut );
}
// プロセス終了なら、ループを抜ける
if( dwRet == WAIT_OBJECT_0 ) break;
}
// プロセスハンドルとスレッドハンドルを閉じる
::GetExitCodeProcess( pi.hProcess, &dwRes );
::CloseHandle( pi.hProcess );
::CloseHandle( pi.hThread );
}
// すべてのパイプを閉じる
::CloseHandle( hWritePipe );
::CloseHandle( hReadPipe );
::CloseHandle( hErrWritePipe );
::CloseHandle( hErrReadPipe );
}
static WSCfunctionRegister op(“Mainvbtn_001_ACTIVATE_EP”,(void*)Mainvbtn_001_ACTIVATE_EP);
なかなか苦労した(わからなかったところ)は、パイプのバッファ設定のところです。
::CreatePipe( &hReadPipe, &hWritePipe, &sa, 0 );
という行があります。この「::CreatePipe()」の最後の引数、バッファサイズを指定するところなんですが、ここでパイプサイズを指定してしまうと、実行されるコマンド側でマメに出力をflushしてくれないとPipeからデータが出てこなくなってしまうのです。最初、てっきりパイプのサイズをちゃんと指定しないといけないと思って数値をまじめに設定してたら、コマンド実行がすべて完了するまでデータが表示されないという状態になってしまい、変だなぁと悩んでしまいました。結論としては0としておけばよいみたいです(MSDNのCreatePipeに、「既定のバッファサイズが割り当てられる」とあります)。
ここで紹介したソースコードはそのまま動作しますが要点だけなので、たとえば、安定してアプリでリアルタイムに出力結果を表示するとか、環境変数などの設定もあわせて行う必要がある場合などは、もっとプログラムを書き足さないといけませんので、そのあたりは研究してみてくださいね。
きっと、GDLなんかも、こんなやり方でGCCやその他のツールを呼び出し、自分の画面で実行結果を表示していると思います。地味なテクニックかもしれませんが、これで自作アプリからgccやavrdudeを呼び出して…ふっふっふ…なんてやると、自分で作った統合環境から既存のアプリをちゃんと統合されている雰囲気で呼び出すことができ、かなり
自己満足度アップ
なのではないかと思います。
「出力結果を使って云々」なんて前に書きましたが、ホントは、ただそれだけのためです。
Your Message