普通のウィンドウアプリケーションをコマンドラインアプリケーションとしても実行させたい場合にとりうる方法として、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)
このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。
ここをクリック
すると表示されるページから作者へメールで連絡できます。