普通のウィンドウアプリケーションをコマンドラインアプリケーションとしても実行させたい場合にとりうる方法として、1 つは、WinMain のかわりに、main で開始するコマンドラインアプリケーションから、ウィンドウを表示する方法が考えられます。
この方法は簡単ですが、実行時に常にコンソールウィンドウが表示されるので、普通のウィンドウアプリケーションとしては実行するのは難しそうです。(GetConsoleWindow function とかを駆使して、非表示にする方法もあるかもしれませんが、試していません)
なので、この記事では、WinMain で開始したまま必要に応じてコンソールを用意することにより、ウィンドウアプリケーションのまま、コマンドラインアプリケーションとしても実行できるようにする方法を解説します。
まず、専用のコンソールを起動時に用意するのが一番簡単です。例えば、コマンドライン引数で /console が指定されている場合のみ専用のコンソールを作成します。専用のコンソールを作成するには、AllocConsole function を使用します。
この方法はとても簡単ですが、普通のコンソールアプリケーションの場合、コンソールから起動した場合には、起動元のコンソールが流用され、新しいコンソールは作成されません。このような挙動を実現するには、AttachConsole function を使用します。
::AttachConsole(ATTACH_PARENT_PROCESS)
結局、必要に応じてコンソールを用意するコードは下記のようになります。
if (::AttachConsole(ATTACH_PARENT_PROCESS) == FALSE) { if (!force) { m_lastError = ::GetLastError(); return; } if (::AllocConsole() == FALSE) { m_lastError = ::GetLastError(); return; } }
bool force
には、親コンソールが無い場合に、自前で用意するかどうかをあらかじめ設定しておくフラグです。DWORD m_lastError
には、失敗した場合、0 以外の値 (エラーコード) が格納されます。また、このコードにはありませんが、AllocConsole、または、AttachConsole で用意したコンソールは、FreeConsole function
により開放できます。
コンソールを用意しただけでは、printf
や std::cout
、std::wcout
などに必要な、stdin, stdout, stderr はまだ、使用できる状態にはなっていません。stdin などを用意したコンソールに繋げるには、freopen_s
を使用します。
FILE* fpOut = NULL; ::freopen_s(&fpOut, "CONOUT$", "w", stdout); FILE* fpErr = NULL; ::freopen_s(&fpErr, "CONOUT$", "w", stderr); FILE* fpIn = NULL; ::freopen_s(&fpIn, "CONIN$", "r", stdin);
freopen_s で開いたファイルは使用後に閉じる必要があるので、下記のようなクラスに閉じるロジックを封じておくと便利です。
#pragma once class CStdFile { public: CStdFile() : m_fp() {} ~CStdFile() { Close(); } void Reopen(const char* path, const char* mode, FILE* std) { // for stdin, stdout, stderr // Close(); ::freopen_s(&m_fp, path, mode, std); } void Close() { if (m_fp != NULL) { ::fclose(m_fp); m_fp = NULL; } } protected: FILE* m_fp; };
ウィンドウアプリケーションでも、コンソールからリダイレクトされている場合、すでに、開かれている場合があるので、その場合は、reopen_s しません。結局、stdout などを reopen するコードは下記のようになります。m_stdout、m_stderr、m_stdin は前で定義した、CStdFile クラスのインスタンスです。
if (_fileno(stdout) < 0) m_stdout.Reopen("CONOUT$", "w", stdout); if (_fileno(stderr) < 0) m_stderr.Reopen("CONOUT$", "w", stderr); if (_fileno(stdin) < 0) m_stdin.Reopen("CONIN$", "r", stdin);
例えば、/console を指定すると、メインウィンドウを表示しないアプリケーションは、ウィンドウを非表示で作成した後、ShowWindow 関数 しなければよいだけなので簡単です。注意点としては、SetWindowPos 関数 を呼び出すときに、SWP_NOACTIVATE を指定しなかったり、ダイアログのウィンドウプロシージャーの WM_INITDIALOG message (Windows) で、TRUE を返したりすると、非表示のウィンドウにもフォーカスが移る!ので注意が必要です。見えないウィンドウにフォーカスが移動している場合、キーボードに反応して、ユーザーからして見ると意味不明なアクションが起きるので、注意が必要です。例えば、メインダイアログの最初のコントロールがカレントドライブをフォーマットするボタンだったと想像してみてください。恐ろしいですね。
以上で、実装は終わりですが、まだコマンドラインアプリケーションと挙動が異なる部分があります。それは、コンソールから実行した場合、コマンドラインアプリケーションは実行が終了するまで待つのに対し、ウィンドウアプリケーションでは、終了を待たずに、次のコマンドを受け付ける状態になってしまう点です。
この状態のコンソールに AttachConsole した場合、標準出力結果も見づらくなりますし、標準入力の受け付けもおかしくなるので、困りものですが、これをウィンドウアプリケーションの実装でどうにかする方法は、よくわかりませんでした。
この状態は、Start コマンドに /wait オプションを指定してウィンドウアプリケーションを実行すると回避できます。また、バッチファイルから起動すると、Start コマンドを使用しなくても、待ってくれるみたいなので、そちらの方が簡単です。
start /wait WindowApplicationSample.exe /console
同様に、vbs から呼び出す場合は、Wscript.Shell オブジェクトの Run メソッド最後の引数を True にすれば、待つことができます。
Dim wsh Set wsh = Wscript.CreateObject("Wscript.Shell") Dim ret ret = wsh.Run("%comspec% /c " & chr(34) & cmdLine & chr(34), 1, True)
このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。
ここをクリック
すると表示されるページから作者へメールで連絡できます。