web-dev-qa-db-ja.com

AllocConsoleで起動したコンソールを閉じると、アプリケーション全体が終了するのはなぜですか?この動作を変更できますか?

私が望んでいるのは、コンソールウィンドウが消える、または非表示になっていることですが、アプリケーションを実行し続けたいと考えています。それは可能ですか? Console.WriteLineを使用して、コンソールを出力ウィンドウとして機能させたい。私はそれを非表示にして表示できるようにしたいのですが、コンソールが閉じられたという理由だけでアプリ全体が停止することは望んでいません。

[〜#〜]編集[〜#〜]

コード:

internal class SomeClass {

    [DllImport("kernel32")]
    private static extern bool AllocConsole();

    private static void Main() {
        AllocConsole();
        while(true) continue;
    }
}

編集2

この質問へのコメントの提案に従って、ここで受け入れられた解決策を試しました[ Capture console exit C# ]。サンプルコードには、DLLImportが「Kernel32」ではなく「kernel32.dll」または「kernel32」である必要があるというバグがあります。その変更を行った後、コンソールウィンドウの[X]をクリックすると、CTRL_CLOSE_EVENTのメッセージがハンドラーに表示されます。ただし、FreeConsoleを呼び出したり、trueを返したりしても、アプリケーションの終了は妨げられません。

27
Kelsie

ああ、そうです、これはWindowsコンソールサブシステムを使用する際の注意点の1つです。ユーザーがコンソールウィンドウを閉じると(コンソールの割り当て方法に関係なく)、コンソールに接続されているすべてのプロセスが終了します。この動作は、コンソールアプリケーション(つまり、標準のWindowsアプリケーションではなく、コンソールサブシステムを特に対象とするアプリケーション)にとっては明らかに理にかなっていますが、あなたのような場合には大きな問題になる可能性があります。

私が知っている唯一の回避策は、 SetConsoleCtrlHandler function を使用することです。これにより、次のハンドラー関数を登録できます。 Ctrl+C そして Ctrl+Break シグナル、およびユーザーがコンソールウィンドウを閉じる、ユーザーがログオフする、システムをシャットダウンするなどのシステムイベント。ドキュメントには、これらのイベントを無視することにのみ関心がある場合は、最初の引数にnullを渡すことができると記載されています。例えば:

[DllImport("kernel32")]
static extern bool SetConsoleCtrlHandler(HandlerRoutine HandlerRoutine, bool Add);

delegate bool HandlerRoutine(uint dwControlType);

static void Main()
{
    AllocConsole();
    SetConsoleCtrlHandler(null, true);
    while (true) continue;
}

それは完璧に機能します Ctrl+C そして Ctrl+Break シグナル(そうでなければアプリケーションも終了する原因になります)が、ユーザーがコンソールウィンドウを閉じたときにシステムによって生成されるCTRL_CLOSE_EVENTである、要求しているものでは機能しません。 。

正直なところ、それを防ぐ方法がわかりません。 SDKのサンプル でさえ、実際にはCTRL_CLOSE_EVENTを無視することはできません。小さなテストアプリで試してみましたが、ウィンドウを閉じてメッセージを出力するとビープ音が鳴りますが、それでもプロセスは終了します。

おそらくもっと心配なことに、ドキュメントはこれを防ぐことは不可能だと私に思わせます:

システムは、ユーザーがコンソールを閉じるか、ログオフするか、システムをシャットダウンすると、CTRL_CLOSE_EVENTCTRL_LOGOFF_EVENT、およびCTRL_SHUTDOWN_EVENTシグナルを生成するため、プロセスは終了前にクリーンアップする機会があります。コンソール関数、またはコンソール関数を呼び出すCランタイム関数は、前述の3つの信号のいずれかの処理中に確実に機能しない場合があります。その理由は、プロセスシグナルハンドラを実行する前に、内部コンソールクリーンアップルーチンの一部またはすべてが呼び出された可能性があるためです。

私の目を引くのはその最後の文です。ユーザーがウィンドウを閉じようとしたことに応答して、コンソールサブシステムがそれ自体の後ですぐにクリーンアップを開始した場合、事後にそれを停止できない場合があります。

(少なくとも今、あなたは問題を理解しています。たぶん誰か他の人が解決策を持ってくることができます!)

27
Cody Gray

残念ながら、この動作を実際に変更するためにできることは何もありません。

コンソールウィンドウは、別のプロセスによってホストされ、サブクラス化を許可しないという点で「特別」です。これは彼らの行動を変えるあなたの能力を制限します。

私が知っていることから、あなたの2つのオプションは次のとおりです。

1.閉じるボタンを完全に無効にします。これは、次のコードフラグメントを使用して実行できます。

 HWND hwnd = :: GetConsoleWindow(); 
 if(hwnd!= NULL)
 {
 HMENU hMenu = :: GetSystemMenu(hwnd、FALSE); 
 if(hMenu!= NULL)DeleteMenu(hMenu、SC_CLOSE、MF_BYCOMMAND); 
} 

2.コンソールの使用を完全に停止し、独自のテキスト出力ソリューションを実装します。

オプション#2はより複雑なオプションですが、最大の制御を提供します。 CodeProject で、リッチエディットコントロールを使用してテキストを表示するコンソールのようなアプリケーションを実装する記事を見つけました(リッチエディットコントロールにはコンソールのようにテキストをストリーミングする機能があるため、これに適しています一種のアプリケーション)。

23
Jonathan Potter

AllocConsoleまたはAttachConsoleを使用して取得したコンソールウィンドウを閉じると、関連するプロセスが終了します。それから逃れることはできません。

Windows Vistaより前は、コンソールウィンドウを閉じると、プロセスを終了するかどうかを尋ねる確認ダイアログがユーザーに表示されますが、Windows Vista以降ではそのようなダイアログが表示されず、プロセスが終了します。

これを回避するための1つの可能な解決策は、AttachConsoleを完全に回避し、他の方法で目的の機能を実現することです。

たとえば、OPで説明されているケースでは、Console静的クラスを使用してコンソールにテキストを出力するためにコンソールウィンドウが必要でした。

これは、プロセス間通信を使用して非常に簡単に実現できます。たとえば、エコーサーバーとして機能するコンソールアプリケーションを開発できます。

namespace EchoServer
{
    public class PipeServer
    {
        public static void Main()
        {
            var pipeServer = new NamedPipeServerStream(@"Com.MyDomain.EchoServer.PipeServer", PipeDirection.In);
            pipeServer.WaitForConnection();

            StreamReader reader = new StreamReader(pipeServer);

            try
            {
                int i = 0;
                while (i >= 0)
                {
                    i = reader.Read();
                    if (i >= 0)
                    {
                        Console.Write(Convert.ToChar(i));
                    }
                }
            }
            catch (IOException)
            {
                //error handling code here
            }
            finally
            {
                pipeServer.Close();
            }
        }
    }
} 

次に、コンソールを現在のアプリケーションに割り当て/接続する代わりに、アプリケーション内からエコーサーバーを起動してConsole's出力ストリームは、パイプサーバーに書き込むようにリダイレクトできます。

class Program
{
    private static NamedPipeClientStream _pipeClient;

    static void Main(string[] args)
    {
        //Current application is a Win32 application without any console window
        var processStartInfo = new ProcessStartInfo("echoserver.exe");

        Process serverProcess = new Process {StartInfo = processStartInfo};
        serverProcess.Start();

        _pipeClient = new NamedPipeClientStream(".", @"Com.MyDomain.EchoServer.PipeServer", PipeDirection.Out, PipeOptions.None);
        _pipeClient.Connect();
        StreamWriter writer = new StreamWriter(_pipeClient) {AutoFlush = true};
        Console.SetOut(writer);

        Console.WriteLine("Testing");

        //Do rest of the work. 
        //Also detect that the server has terminated (serverProcess.HasExited) and then close the _pipeClient
        //Also remember to terminate the server process when current process exits, serverProcess.Kill();
        while (true)
            continue;
    }
}

これは、考えられる解決策の1つにすぎません。基本的に、回避策は、コンソールウィンドウを独自のプロセスに割り当てて、親プロセスに影響を与えずに終了できるようにすることです。

10
Amit Mittal