web-dev-qa-db-ja.com

Windows上のアプリケーションにctrl-C(SIGINT)を送信できますか?

(過去に)クロスプラットフォーム(Windows/Unix)アプリケーションを作成しました。これは、コマンドラインから起動したときに、ユーザーが入力した Ctrl-C 同じ方法で組み合わせます(つまり、アプリケーションを完全に終了します)。

Windowsで送信することは可能ですか? Ctrl-C/ SIGINT /きれいに終了することを要求する別の(無関係な)プロセスからのプロセスと同等(リソースを整理する機会を与えるなど)?

81
Matthew Murdoch

私が解決策に最も近づいたのは、 SendSignal サードパーティのアプリです。著者は、ソースコードと実行可能ファイルをリストします。 64ビットウィンドウで動作することを確認しました(32ビットプログラムとして実行し、別の32ビットプログラムを強制終了します)が、コードをWindowsプログラムに埋め込む方法(32ビットまたはまたは64ビット)。

使い方:

デバッガーをよく調べてみると、ctrl-breakのような信号に関連する動作を実際に行うエントリポイントはkernel32!CtrlRoutineであることがわかりました。この関数はThreadProcと同じプロトタイプを持っているため、コードを挿入することなく、CreateRemoteThreadで直接使用できます。ただし、それはエクスポートされたシンボルではありません! Windowsのバージョンが異なると、アドレスも異なります(名前も異なります)。何をすべきか?

これが私がついに思いついた解決策です。アプリのコンソールctrlハンドラーをインストールし、アプリのctrl-breakシグナルを生成します。ハンドラーが呼び出されると、スタックの一番上を振り返って、kernel32!BaseThreadStartに渡されたパラメーターを見つけます。最初のパラメーターを取得します。これは、スレッドの目的の開始アドレスであり、kernel32!CtrlRoutineのアドレスです。その後、ハンドラーから戻り、シグナルを処理したのでアプリを終了しないことを示します。メインスレッドに戻り、kernel32!CtrlRoutineのアドレスが取得されるまで待ちます。取得したら、検出された開始アドレスを使用して、ターゲットプロセスにリモートスレッドを作成します。これにより、ターゲットプロセスのctrlハンドラーがctrl-breakが押されたかのように評価されます!

良い点は、ターゲットプロセスのみが影響を受け、任意のプロセス(ウィンドウ化されたプロセスも)をターゲットにできることです。 1つの欠点は、kernel32!CtrlRoutineのアドレスを発見するためにctrl-breakイベントを送信するときにそれを強制終了するため、私の小さなアプリはバッチファイルで使用できないことです。

(バッチファイルで実行する場合は、startを先頭に付けます。)

27
arolson101

私はこのトピックに関していくつかの研究を行ってきましたが、予想よりも人気がありました。 KindDragonの回答は重要なポイントの1つでした。

このトピックについて 長いブログ投稿 を書いて、このタイプのシステムを使用していくつかの素敵な方法でコマンドラインアプリケーションを閉じる方法を示す実用的なデモプログラムを作成しました。この投稿には、研究で使用した外部リンクもリストされています。

要するに、これらのデモプログラムは次のことを行います。

  • .Netを使用して可視ウィンドウでプログラムを開始し、pinvokeで非表示にし、6秒間実行し、pinvokeで表示し、.Netで停止します。
  • .Netを使用してウィンドウなしでプログラムを起動し、6秒間実行し、コンソールを接続してConsoleCtrlEventを発行して停止します

Edit:このコードに興味がある人のためのKindDragonの修正されたソリューション。最初のプログラムを停止した後に他のプログラムを開始する場合は、Ctrl-C処理を再度有効にする必要があります。そうしないと、次のプロセスは親の無効状態を継承し、Ctrl-Cに応答しません。

_[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();

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

delegate bool ConsoleCtrlDelegate(CtrlTypes CtrlType);

// Enumerated type for the control messages sent to the handler routine
enum CtrlTypes : uint
{
  CTRL_C_EVENT = 0,
  CTRL_BREAK_EVENT,
  CTRL_CLOSE_EVENT,
  CTRL_LOGOFF_EVENT = 5,
  CTRL_SHUTDOWN_EVENT
}

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

public void StopProgram(Process proc)
{
  //This does not require the console window to be visible.
  if (AttachConsole((uint)proc.Id))
  {
    // Disable Ctrl-C handling for our program
    SetConsoleCtrlHandler(null, true); 
    GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);

    //Moved this command up on suggestion from Timothy Jannace (see comments below)
    FreeConsole();

    // Must wait here. If we don't and re-enable Ctrl-C
    // handling below too fast, we might terminate ourselves.
    proc.WaitForExit(2000);

    //Re-enable Ctrl-C handling or any subsequently started
    //programs will inherit the disabled state.
    SetConsoleCtrlHandler(null, false); 
  }
}
_

また、AttachConsole()または送信された信号が失敗する場合、たとえば次のようにスリープする場合、不測の事態に対するソリューションを計画します。

_if (!proc.HasExited)
{
  try
  {
    proc.Kill();
  }
  catch (InvalidOperationException e){}
}
_
52
Stanislav

私はこの質問に少し遅れていると思いますが、同じ問題を抱えている人のためにとにかく何かを書きます。これは this の質問に答えたのと同じ答えです。

私の問題は、アプリケーションをGUIアプリケーションにしたいのですが、実行されるプロセスは、対話型コンソールウィンドウが接続されていない状態でバックグラウンドで実行する必要があるということでした。このプロセスは、親プロセスがコンソールプロセスの場合にも機能するはずです。ただし、「CREATE_NO_WINDOW」フラグを削除する必要がある場合があります。

ラッパーアプリで GenerateConsoleCtrlEvent ()を使用してこれを解決できました。トリッキーな部分は、ドキュメンテーションが実際にどのように使用できるのか、そしてそれによる落とし穴について本当に明確ではないということです。

私の解決策は、記述されているものに基づいています ここ 。しかし、それでもすべての詳細を説明することはできず、エラーが発生したため、ここでそれを機能させる方法の詳細を説明します。

新しいヘルパーアプリケーション「Helper.exe」を作成します。このアプリケーションは、アプリケーション(親)と閉じたい子プロセスの間に配置されます。また、実際の子プロセスも作成します。この「中間者」プロセスが必要です。そうでない場合、GenerateConsoleCtrlEvent()は失敗します。

何らかの種類のIPCメカニズムを使用して、ヘルパーが子プロセスを閉じる必要があることを親プロセスからヘルパープロセスに伝えます。ヘルパーがこのイベントを取得すると、「GenerateConsoleCtrlEvent(CTRL_BREAK、0)」自分自身と子プロセスを閉じます。子プロセスをキャンセルしたいときに親が完了するイベントオブジェクトを自分で使用しました。

Helper.exeを作成するには、CREATE_NO_WINDOWおよびCREATE_NEW_PROCESS_GROUPを使用して作成します。そして、子プロセスを作成するとき、フラグなし(0)で作成します。これは、親からコンソールを派生させることを意味します。これを行わないと、イベントが無視されます。

各ステップがこのように行われることが非常に重要です。さまざまな種類の組み合わせを試してきましたが、この組み合わせが唯一の組み合わせです。 CTRL_Cイベントを送信できません。成功を返しますが、プロセスでは無視されます。 CTRL_BREAKのみが機能します。どちらも最後にExitProcess()を呼び出すため、実際には問題ではありません。

また、子プロセスIDのプロセスグループIDでGenerateConsoleCtrlEvent()を直接呼び出すことはできません。これにより、ヘルパープロセスが生き続けることができます。これも失敗します。

これを機能させるために丸一日を費やしました。このソリューションは私には有効ですが、誰か他に何か追加することがあれば、してください。同様の問題を抱えているが問題の明確な解決策を持たない多くの人々を見つけるために、私はあちこちに行きました。 GenerateConsoleCtrlEvent()の動作も少し奇妙なので、誰かがそれについての詳細を知っているなら共有してください。

17
Shakta

編集:

GUIアプリの場合、Windows開発でこれを処理する「通常の」方法は、プロセスのメインウィンドウにWM_CLOSEメッセージを送信することです。

コンソールアプリの場合は、 SetConsoleCtrlHandler を使用してCTRL_C_EVENTを追加する必要があります。

アプリケーションがそれを尊重しない場合は、 TerminateProcess を呼び出すことができます。

7
Reed Copsey

何らかの方法でGenerateConsoleCtrlEvent()が別のプロセスに対して呼び出すとエラーを返しますが、別のコンソールアプリケーションに接続して、すべての子プロセスにイベントを送信できます。

void SendControlC(int pid)
{
    AttachConsole(pid); // attach to process console
    SetConsoleCtrlHandler(NULL, TRUE); // disable Control+C handling for our app
    GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // generate Control+C event
}
6
KindDragon

C++アプリで使用するコードは次のとおりです。

正のポイント:

  • コンソールアプリから動作
  • Windowsサービスから動作します
  • 遅延は必要ありません
  • 現在のアプリを閉じません

マイナスポイント:

  • メインコンソールが失われ、新しいコンソールが作成されます( FreeConsole を参照)
  • コンソールの切り替えは奇妙な結果をもたらします...

// Inspired from http://stackoverflow.com/a/15281070/1529139
// and http://stackoverflow.com/q/40059902/1529139
bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent)
{
    bool success = false;
    DWORD thisConsoleId = GetCurrentProcessId();
    // Leave current console if it exists
    // (otherwise AttachConsole will return ERROR_ACCESS_DENIED)
    bool consoleDetached = (FreeConsole() != FALSE);

    if (AttachConsole(dwProcessId) != FALSE)
    {
        // Add a fake Ctrl-C handler for avoid instant kill is this console
        // WARNING: do not revert it or current program will be also killed
        SetConsoleCtrlHandler(nullptr, true);
        success = (GenerateConsoleCtrlEvent(dwCtrlEvent, 0) != FALSE);
        FreeConsole();
    }

    if (consoleDetached)
    {
        // Create a new console if previous was deleted by OS
        if (AttachConsole(thisConsoleId) == FALSE)
        {
            int errorCode = GetLastError();
            if (errorCode == 31) // 31=ERROR_GEN_FAILURE
            {
                AllocConsole();
            }
        }
    }
    return success;
}

使用例:

DWORD dwProcessId = ...;
if (signalCtrl(dwProcessId, CTRL_C_EVENT))
{
    cout << "Signal sent" << endl;
}
5
56ka
        void SendSIGINT( HANDLE hProcess )
        {
            DWORD pid = GetProcessId(hProcess);
            FreeConsole();
            if (AttachConsole(pid))
            {
                // Disable Ctrl-C handling for our program
                SetConsoleCtrlHandler(NULL, true);

                GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0); // SIGINT

                //Re-enable Ctrl-C handling or any subsequently started
                //programs will inherit the disabled state.
                SetConsoleCtrlHandler(NULL, false);

                WaitForSingleObject(hProcess, 10000);
            }
        }

現時点ではそうではないので、透明にする必要があります。 Ctrl-Cを送信するためのSendSignalの修正およびコンパイルされたバージョンがあります(デフォルトではCtrl + Breakのみを送信します)。以下にいくつかのバイナリを示します。

(2014-3-7):Ctrl-Cを使用して32ビット版と64ビット版の両方をビルドしました。SendSignalCtrlC.exeと呼ばれ、次の場所からダウンロードできます。 https://dl.dropboxusercontent.com/ u/49065779/sendsignalctrlc/x86/SendSignalCtrlC.exehttps://dl.dropboxusercontent.com/u/49065779/sendsignalctrlc/x86_64/SendSignalCtrlC.exe -Juraj Michalak

念のため、これらのファイルもミラーリングしています。
32ビットバージョン: https://www.dropbox.com/s/r96jxglhkm4sjz2/SendSignalCtrlC.exe?dl=
64ビットバージョン: https://www.dropbox.com/s/hhe0io7mcgcle1c/SendSignalCtrlC64.exe?dl=

免責事項:これらのファイルは作成しませんでした。コンパイルされた元のファイルは変更されていません。テスト済みのプラットフォームは64ビットWindows 7のみです。 http://www.latenighthacking.com/projects/2003/sendSignal/ で入手可能なソースを調整し、自分でコンパイルすることをお勧めします。

3
ioikka

Javaで、C++ソリューションと同様に、Kernel32.dllライブラリでJNAを使用します。 CtrlCSenderメインメソッドをプロセスとして実行し、プロセスのコンソールを取得してCtrl + Cイベントを送信し、イベントを生成します。コンソールなしで個別に実行されるため、Ctrl + Cイベントを無効にして再度有効にする必要はありません。

CtrlCSender.Java- Nemo1024's および KindDragon's 回答に基づきます。

既知のプロセスIDを指定すると、このコンソールアプリケーションはターゲットプロセスのコンソールをアタッチし、CTRL + Cイベントを生成します。

import com.Sun.jna.platform.win32.Kernel32;    

public class CtrlCSender {

    public static void main(String args[]) {
        int processId = Integer.parseInt(args[0]);
        Kernel32.INSTANCE.AttachConsole(processId);
        Kernel32.INSTANCE.GenerateConsoleCtrlEvent(Kernel32.CTRL_C_EVENT, 0);
    }
}

メインアプリケーション-CtrlCSenderを個別のコンソールプロセスとして実行

ProcessBuilder pb = new ProcessBuilder();
pb.command("javaw", "-cp", System.getProperty("Java.class.path", "."), CtrlCSender.class.getName(), processId);
pb.redirectErrorStream();
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Process ctrlCProcess = pb.start();
ctrlCProcess.waitFor();
3
Patimo

はい。 windows-kill プロジェクトはまさにあなたが望むことをします:

windows-kill -SIGINT 1234
2
BullyWiiPlaza

私の友人が問題を解決するための完全に異なる方法を提案し、それは私のために働いた。以下のようなvbscriptを使用します。アプリケーションを起動し、7秒間実行してからctrl + cを使用して閉じます。

'VBScriptの例

Set WshShell = WScript.CreateObject("WScript.Shell")

WshShell.Run "notepad.exe"

WshShell.AppActivate "notepad"

WScript.Sleep 7000

WshShell.SendKeys "^C"
1
Frederic Simard

私はこれがすべて複雑すぎると感じたため、 SendKeys を使用して CTRL-C 回避策としてコマンドラインウィンドウ(つまり、cmd.exeウィンドウ)にキーストロークします。

1
thomiel

プロセスIDに基づいて、プロセスにシグナルを送信して、強制的または正常に終了するか、その他のシグナルを送信できます。

すべてのプロセスをリストします。

C:\>tasklist

プロセスを強制終了するには:

C:\>Taskkill /IM firefox.exe /F
or
C:\>Taskkill /PID 26356 /F

詳細:

http://tweaks.com/windows/39559/kill-processes-from-command-Prompt/

1
Awanish Kumar