31

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

編集

コード:

internal class SomeClass {

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

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

編集2

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

4

4 に答える 4

30

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

私が知っている唯一の回避策は、SetConsoleCtrlHandler関数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これは+信号CCtrl+信号に対しては完全に機能しますが(そうでなければアプリケーションも終了します)、ユーザーが閉じたときにシステムによって生成される、Break要求している信号に対しては機能しません。CTRL_CLOSE_EVENTコンソールウィンドウ。

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

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

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

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

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

于 2012-08-14T20:33:40.717 に答える
25

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

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

私が知っていることから、あなたの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に関する記事を見つけました(リッチエディットコントロールにはコンソールのようにテキストをストリーミングする機能があるため、この種のアプリケーションに適しています)。

于 2012-08-18T01:41:38.900 に答える
11

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つにすぎません。基本的に、回避策は、コンソールウィンドウを独自のプロセスに割り当てて、親プロセスに影響を与えずに終了できるようにすることです。

于 2012-08-22T07:43:02.220 に答える
-1

これを行うには、Keyfreezと呼ばれる外部プログラムによるキーボードマウス入力を無効にします。

ユーザー入力が不要なプログラムで複数回使用できます。また、ユーザー入力が必要な場合は、プロセスTakskkill / f/IMを追加できます。

https://www.sordum.org/7921/bluelife-keyfreeze-v1-4-block-keyboard-and-mouse/

これが皆さんのお役に立てば幸いです

于 2022-02-18T19:48:20.523 に答える